All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.material.plugins.ShaderNodeLoaderDelegate Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2009-2021 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.material.plugins;

import com.jme3.asset.AssetManager;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.asset.ShaderNodeDefinitionKey;
import com.jme3.material.MatParam;
import com.jme3.material.MaterialDef;
import com.jme3.material.ShaderGenerationInfo;
import com.jme3.material.TechniqueDef;
import com.jme3.shader.Shader.ShaderType;
import com.jme3.shader.*;
import com.jme3.util.blockparser.Statement;

import java.io.IOException;
import java.util.*;

/**
 * This class is here to be able to load shaderNodeDefinition from both the
 * J3MLoader and ShaderNodeDefinitionLoader.
 *
 * It also allows loading shader nodes from a j3md file and building the
 * ShaderNodes list of each technique and the ShaderGenerationInfo needed to
 * generate shaders.
 *
 * @author Nehon
 */
public class ShaderNodeLoaderDelegate {

    private static final boolean[] IM_HAS_NAME_SPACE = {false, true};
    private static final boolean[] OM_HAS_NAME_SPACE = {true, false};

    protected Map nodeDefinitions;
    protected Map nodes;
    protected ShaderNodeDefinition shaderNodeDefinition;
    protected ShaderNode shaderNode;
    protected TechniqueDef techniqueDef;
    protected Map attributes = new HashMap<>();
    protected Map vertexDeclaredUniforms = new HashMap<>();
    protected Map fragmentDeclaredUniforms = new HashMap<>();
    protected Map varyings = new HashMap<>();
    protected MaterialDef materialDef;
    protected String shaderLanguage;
    protected String shaderName;
    protected Set varNames = new HashSet<>();
    protected AssetManager assetManager;
    protected ConditionParser conditionParser = new ConditionParser();
    protected List nulledConditions = new ArrayList<>();

    protected class DeclaredVariable {

        ShaderNodeVariable var;
        List nodes = new ArrayList<>();

        public DeclaredVariable(ShaderNodeVariable var) {
            this.var = var;
        }

        public final void addNode(ShaderNode c) {
            if (!nodes.contains(c)) {
                nodes.add(c);
            }
        }
    }
    /**
     * Read the ShaderNodesDefinitions block and returns a list of
     * ShaderNodesDefinition This method is used by the j3sn loader
     *
     * note that the order of the definitions in the list is not guaranteed.
     *
     * @param statements the list statements to parse
     * @param key the ShaderNodeDefinitionKey
     * @return a list of ShaderNodesDefinition
     * @throws IOException if an I/O error occurs
     */
    public List readNodesDefinitions(List statements, ShaderNodeDefinitionKey key) throws IOException {

        for (Statement statement : statements) {
            String[] split = statement.getLine().split("[ \\{]");
            if (statement.getLine().startsWith("ShaderNodeDefinition")) {
                String name = statement.getLine().substring("ShaderNodeDefinition".length()).trim();


                if (!getNodeDefinitions().containsKey(name)) {
                    shaderNodeDefinition = new ShaderNodeDefinition();
                    getNodeDefinitions().put(name, shaderNodeDefinition);
                    shaderNodeDefinition.setName(name);
                    shaderNodeDefinition.setPath(key.getName());
                    readShaderNodeDefinition(statement.getContents(), key);

                }
            } else {
                throw new MatParseException("ShaderNodeDefinition", split[0], statement);
            }
        }

        return new ArrayList(getNodeDefinitions().values());
    }

    /**
     * Read the ShaderNodesDefinitions block and internally stores a map of
     * ShaderNodesDefinition This method is used by the j3m loader.
     *
     * When loaded in a material, the definitions are not stored as a list, but
     * they are stored in shader nodes based on this definition.
     *
     * The map is here to map the definition to the nodes, and ovoid reloading
     * already loaded definitions
     *
     * @param statements the list of statements to parse
     * @throws IOException if an I/O error occurs
     */
    public void readNodesDefinitions(List statements) throws IOException {
        readNodesDefinitions(statements, new ShaderNodeDefinitionKey());
    }

    /**
     * effectively reads the ShaderNodesDefinitions block
     *
     * @param statements the list of statements to parse
     * @param key the ShaderNodeDefinitionKey
     * @throws IOException if an I/O error occurs
     */
    protected void readShaderNodeDefinition(List statements, ShaderNodeDefinitionKey key) throws IOException {
        boolean isLoadDoc = key instanceof ShaderNodeDefinitionKey && key.isLoadDocumentation();
        for (Statement statement : statements) {
            try {
                String[] split = statement.getLine().split("[ \\{]");
                String line = statement.getLine();

                if (line.startsWith("Type")) {
                    String type = line.substring(line.lastIndexOf(':') + 1).trim();
                    shaderNodeDefinition.setType(ShaderType.valueOf(type));
                } else if (line.startsWith("Shader ")) {
                    readShaderStatement(statement);
                    shaderNodeDefinition.getShadersLanguage().add(shaderLanguage);
                    shaderNodeDefinition.getShadersPath().add(shaderName);
                } else if (line.startsWith("Documentation")) {
                    if (isLoadDoc) {
                        String doc = "";
                        for (Statement statement1 : statement.getContents()) {
                            doc += "\n" + statement1.getLine();
                        }
                        shaderNodeDefinition.setDocumentation(doc);
                    }
                } else if (line.startsWith("Input")) {
                    varNames.clear();
                    for (Statement statement1 : statement.getContents()) {
                        try {
                            shaderNodeDefinition.getInputs().add(readVariable(statement1));
                        } catch (RuntimeException e) {
                            throw new MatParseException(e.getMessage(), statement1, e);
                        }
                    }
                } else if (line.startsWith("Output")) {
                    varNames.clear();
                    for (Statement statement1 : statement.getContents()) {
                        try {
                            if (statement1.getLine().trim().equals("None")) {
                                shaderNodeDefinition.setNoOutput(true);
                            } else {
                                shaderNodeDefinition.getOutputs().add(readVariable(statement1));
                            }
                        } catch (RuntimeException e) {
                            throw new MatParseException(e.getMessage(), statement1, e);
                        }
                    }
                } else {
                    throw new MatParseException("one of Type, Shader, Documentation, Input, Output", split[0], statement);
                }
            } catch (RuntimeException e) {
                throw new MatParseException(e.getMessage(), statement, e);
            }
        }
    }

    /**
     * reads a variable declaration statement <glslType> <varName>
     *
     * @param statement the statement to parse
     * @return a ShaderNodeVariable extracted from the statement
     * @throws IOException if an I/O error occurs
     */
    protected ShaderNodeVariable readVariable(Statement statement) throws IOException {

        String line = statement.getLine().trim().replaceAll("\\s*\\[", "[");
        String[] splitVar = line.split("\\s");

        if (splitVar.length > 3) {
            throw new MatParseException("More than 3 arguments", splitVar.length + "", statement);
        }

        String defaultValue = splitVar.length > 2? splitVar[2] : null;
        String varName = splitVar[1];
        String varType = splitVar[0];
        String multiplicity = null;

        if (varName.contains("[")) {
            //we have an array
            String[] arr = splitVar[1].split("\\[");
            varName = arr[0].trim();
            multiplicity = arr[1].replaceAll("\\]", "").trim();
        }

        if (varNames.contains(varName)) {
            throw new MatParseException("Duplicate variable name " + varName, statement);
        }

        varNames.add(varName);

        final ShaderNodeVariable variable = new ShaderNodeVariable(varType, "", varName, multiplicity);
        variable.setDefaultValue(defaultValue);

        return variable;
    }

    /**
     * reads the VertexShaderNodes{} block
     *
     * @param statements the list of statements to parse
     * @throws IOException if an I/O error occurs
     */
    public void readVertexShaderNodes(List statements) throws IOException {
        attributes.clear();
        readNodes(statements);
    }

    /**
     * reads a list of ShaderNode{} blocks
     *
     * @param statements the list of statements to parse
     * @throws IOException if an I/O error occurs
     */
    protected void readShaderNode(List statements) throws IOException {

        final ShaderGenerationInfo generationInfo = techniqueDef.getShaderGenerationInfo();
        final List unusedNodes = generationInfo.getUnusedNodes();

        for (Statement statement : statements) {

            String line = statement.getLine();
            String[] split = statement.getLine().split("[ \\{]");

            if (line.startsWith("Definition")) {
                ShaderNodeDefinition def = findDefinition(statement);
                shaderNode.setDefinition(def);
                if(def.isNoOutput()){
                    unusedNodes.remove(shaderNode.getName());
                }
            } else if (line.startsWith("Condition")) {
                String condition = line.substring(line.lastIndexOf(":") + 1).trim();
                extractCondition(condition, statement);
                shaderNode.setCondition(conditionParser.getFormattedExpression());
            } else if (line.startsWith("InputMappings")) {
                for (final Statement subStatement : statement.getContents()) {

                    VariableMapping mapping = readInputMapping(subStatement);

                    final ShaderNodeVariable rightVariable = mapping.getRightVariable();
                    if (rightVariable != null) {
                        unusedNodes.remove(rightVariable.getNameSpace());
                    }

                    shaderNode.getInputMapping().add(mapping);
                }
            } else if (line.startsWith("OutputMappings")) {
                for (Statement statement1 : statement.getContents()) {
                    VariableMapping mapping = readOutputMapping(statement1);
                    unusedNodes.remove(shaderNode.getName());
                    shaderNode.getOutputMapping().add(mapping);
                }
            } else {
                throw new MatParseException("ShaderNodeDefinition", split[0], statement);
            }
        }
    }

    /**
     * Reads a mapping statement. Sets the nameSpace, name and swizzling of the
     * left variable. Sets the name, nameSpace and swizzling of the right
     * variable types will be determined later. Also, we can have the right part as expression.
     * 
     * Format variable to variable: <nameSpace>.<varName>[.<swizzling>] = <nameSpace>.<varName>[.<swizzling>][:Condition]
     * Format expression to variable: <nameSpace>.<varName>[.<swizzling>] = %% expression %% [:Condition]
     * 
* * @param statement the statement to read. * @param hasNameSpace indicate which vars have namespaces * @return the read mapping. * @throws MatParseException if the statement isn't valid. */ protected VariableMapping parseMapping(Statement statement, boolean[] hasNameSpace) throws MatParseException { VariableMapping mapping = new VariableMapping(); String[] cond = statement.getLine().split(":"); String[] vars = cond[0].split("="); checkMappingFormat(vars, statement); ShaderNodeVariable[] variables = new ShaderNodeVariable[2]; String[] swizzle = new String[2]; String rightExpression = null; for (int i = 0; i < vars.length; i++) { final String var = vars[i].trim(); // it seems that is expression, not variable if (var.contains("%%")) { rightExpression = var.substring(2, var.length() - 2); continue; } String[] expression = var.split("\\."); if (hasNameSpace[i]) { if (expression.length <= 3) { variables[i] = new ShaderNodeVariable("", expression[0].trim(), expression[1].trim()); } if (expression.length == 3) { swizzle[i] = expression[2].trim(); } } else { if (expression.length <= 2) { variables[i] = new ShaderNodeVariable("", expression[0].trim()); } if (expression.length == 2) { swizzle[i] = expression[1].trim(); } } } mapping.setLeftVariable(variables[0]); mapping.setLeftSwizzling(swizzle[0] != null ? swizzle[0] : ""); if (rightExpression != null) { mapping.setRightExpression(rightExpression); } else { mapping.setRightVariable(variables[1]); mapping.setRightSwizzling(swizzle[1] != null ? swizzle[1] : ""); } if (cond.length > 1) { extractCondition(cond[1], statement); mapping.setCondition(conditionParser.getFormattedExpression()); } return mapping; } /** * reads the FragmentShaderNodes{} block * * @param statements the list of statements to parse * @throws IOException if an I/O error occurs */ public void readFragmentShaderNodes(List statements) throws IOException { readNodes(statements); } /** * Reads a Shader statement of the form TYPE LANG : SOURCE * * @param statement the shader statement (not null) * @throws IOException if an I/O error occurs */ protected void readShaderStatement(Statement statement) throws IOException { String[] split = statement.getLine().split(":"); if (split.length != 2) { throw new MatParseException("Shader statement syntax incorrect", statement); } String[] typeAndLang = split[0].split("\\p{javaWhitespace}+"); if (typeAndLang.length != 2) { throw new MatParseException("Shader statement syntax incorrect", statement); } shaderName = split[1].trim(); shaderLanguage = typeAndLang[1]; } /** * Sets the technique definition currently being loaded * * @param techniqueDef the technique def */ public void setTechniqueDef(TechniqueDef techniqueDef) { this.techniqueDef = techniqueDef; } /** * sets the material def currently being loaded * * @param materialDef (alias created) */ public void setMaterialDef(MaterialDef materialDef) { this.materialDef = materialDef; } /** * Searches a variable in the given list and updates its type and namespace. * * @param var the variable to update. * @param list the variables list. * @return true if the variable has been found and updated. */ protected boolean updateVariableFromList(ShaderNodeVariable var, List list) { for (ShaderNodeVariable shaderNodeVariable : list) { if (shaderNodeVariable.getName().equals(var.getName())) { var.setType(shaderNodeVariable.getType()); var.setMultiplicity(shaderNodeVariable.getMultiplicity()); var.setNameSpace(shaderNode.getName()); return true; } } return false; } /** * Updates the type of the right variable of a mapping from the type of the * left variable. * * @param mapping the mapping to consider. */ protected void updateRightTypeFromLeftType(VariableMapping mapping) { String type = mapping.getLeftVariable().getType(); int card = ShaderUtils.getCardinality(type, mapping.getRightSwizzling()); if (card > 0) { if (card == 1) { type = "float"; } else { type = "vec" + card; } } mapping.getRightVariable().setType(type); } /** * Checks if once a mapping expression is split by "=" the resulting array * have 2 elements. * * @param vars the array. * @param statement the statement. * @throws MatParseException if the array isn't correct. */ protected void checkMappingFormat(String[] vars, Statement statement) throws MatParseException { if (vars.length != 2) { throw new MatParseException("Not a valid expression should be '[.] = " + ".[.][:Condition]'", statement); } } /** * Finds a {@link MatParam} in the {@link MaterialDef} from the given name. * * @param varName the material param name. * @return the found {@link MatParam} or null. */ protected MatParam findMatParam(String varName) { for (MatParam matParam : materialDef.getMaterialParams()) { if (varName.equals(matParam.getName())) { return matParam; } } return null; } /** * finds an UniformBinding representing a WorldParam from the techniqueDef * * @param varName the name of the WorldParam * @return the corresponding UniformBinding to the WorldParam */ protected UniformBinding findWorldParam(String varName) { for (UniformBinding worldParam : techniqueDef.getWorldBindings()) { if (varName.equals(worldParam.toString())) { return worldParam; } } return null; } /** * updates the right variable of the given mapping from a UniformBinding (a * WorldParam) it checks if the uniform hasn't already been loaded, add it * to the maps if not. * * @param param the WorldParam UniformBinding * @param mapping the mapping * @param map the map of uniforms to search into * @return true if the param was added to the map */ protected boolean updateRightFromUniforms(UniformBinding param, VariableMapping mapping, Map map) { ShaderNodeVariable right = mapping.getRightVariable(); String name = param.toString(); DeclaredVariable dv = map.get(name); if (dv == null) { right.setType(param.getGlslType()); right.setName(name); right.setPrefix("g_"); dv = new DeclaredVariable(right); map.put(right.getName(), dv); dv.addNode(shaderNode); mapping.setRightVariable(right); return true; } dv.addNode(shaderNode); mapping.setRightVariable(dv.var); return false; } /** * Updates the right variable of the given mapping from a {@link MatParam} (a * WorldParam) it checks if the uniform hasn't already been loaded, add it * to the maps if not. * * @param param the mat param. * @param mapping the mapping. * @param map the map of uniforms to search into. * @param statement the statement being read * @return true if the param was added to the map. * @throws MatParseException in case of a syntax error */ public boolean updateRightFromUniforms(MatParam param, VariableMapping mapping, Map map, Statement statement) throws MatParseException { final ShaderNodeVariable left = mapping.getLeftVariable(); final ShaderNodeVariable right = mapping.getRightVariable(); DeclaredVariable dv = map.get(param.getName()); if (dv == null) { right.setType(param.getVarType().getGlslType()); right.setName(param.getName()); right.setPrefix("m_"); if (left.getMultiplicity() != null) { if (!param.getVarType().name().endsWith("Array")) { throw new MatParseException(param.getName() + " is not of Array type", statement); } String multiplicity = left.getMultiplicity(); try { Integer.parseInt(multiplicity); } catch (final NumberFormatException nfe) { // multiplicity is not an int attempting to find for a material parameter. MatParam mp = findMatParam(multiplicity); if (mp != null) { // It's tied to a material param, let's create a define and use this as the multiplicity addDefine(multiplicity, VarType.Int); multiplicity = multiplicity.toUpperCase(); left.setMultiplicity(multiplicity); // only declare the variable if the define is defined. left.setCondition(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); } } // the right variable must have the same multiplicity and the same condition. right.setMultiplicity(multiplicity); right.setCondition(left.getCondition()); } dv = new DeclaredVariable(right); map.put(right.getName(), dv); dv.addNode(shaderNode); mapping.setRightVariable(right); return true; } dv.addNode(shaderNode); mapping.setRightVariable(dv.var); return false; } /** * Updates a variable from the attribute list. * * @param right the variable * @param mapping the mapping */ public void updateVarFromAttributes(ShaderNodeVariable right, VariableMapping mapping) { DeclaredVariable dv = attributes.get(right.getName()); if (dv == null) { dv = new DeclaredVariable(right); attributes.put(right.getName(), dv); updateRightTypeFromLeftType(mapping); } else { mapping.setRightVariable(dv.var); } dv.addNode(shaderNode); } /** * Adds a define to the technique def * * @param paramName the name of the material parameter * @param paramType the type of the material parameter */ public void addDefine(String paramName, VarType paramType) { if (techniqueDef.getShaderParamDefine(paramName) == null) { techniqueDef.addShaderParamDefine(paramName, paramType, paramName.toUpperCase()); } } /** * Finds a variable with the given name from the list of variable. * * @param vars the list of shader node variables. * @param rightVarName the variable name to search for. * @return the found variable or null is not found. */ public ShaderNodeVariable findNodeOutput(List vars, String rightVarName) { ShaderNodeVariable var = null; for (ShaderNodeVariable variable : vars) { if (variable.getName().equals(rightVarName)) { var = variable; } } return var; } /** * Extracts and checks a condition expression. * * @param condition the condition expression. * @param statement the statement being read. * @throws MatParseException if the condition isn't valid. */ public void extractCondition(String condition, Statement statement) throws MatParseException { List defines = conditionParser.extractDefines(condition); for (String string : defines) { MatParam param = findMatParam(string); if (param != null) { addDefine(param.getName(), param.getVarType()); } else { throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + condition, statement); } } } /** * Reads an input mapping. * * @param statement the statement being read. * @return the variable mapping. * @throws MatParseException if we have a problem with parsing input mapping statement. */ public VariableMapping readInputMapping(Statement statement) throws MatParseException { VariableMapping mapping; try { mapping = parseMapping(statement, IM_HAS_NAME_SPACE); } catch (final Exception e) { throw new MatParseException("Unexpected mapping format", statement, e); } final ShaderNodeDefinition definition = shaderNode.getDefinition(); final ShaderNodeVariable left = mapping.getLeftVariable(); final ShaderNodeVariable right = mapping.getRightVariable(); final String expression = mapping.getRightExpression(); if (!updateVariableFromList(left, definition.getInputs())) { throw new MatParseException(left.getName() + " is not an input variable of " + definition.getName(), statement); } else if (left.getType().startsWith("sampler") && (right == null || !right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_MAT_PARAM))) { throw new MatParseException("Samplers can only be assigned to MatParams", statement); } if (right == null && expression == null) { throw new MatParseException("The mapping doesn't have a right variable or a right expression.", statement); } if (right == null) { return mapping; } if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_GLOBAL)) { right.setType("vec4"); // Globals are all vec4 for now (maybe forever...) storeGlobal(right, statement); } else if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_VERTEX_ATTRIBUTE)) { if (definition.getType() == ShaderType.Fragment) { throw new MatParseException("Cannot have an attribute as input in a fragment shader" + right.getName(), statement); } updateVarFromAttributes(mapping.getRightVariable(), mapping); storeAttribute(mapping.getRightVariable()); } else if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_MAT_PARAM)) { MatParam param = findMatParam(right.getName()); if (param == null) { throw new MatParseException("Could not find a Material Parameter named " + right.getName(), statement); } if (definition.getType() == ShaderType.Vertex) { if (updateRightFromUniforms(param, mapping, vertexDeclaredUniforms, statement)) { updateMaterialTextureType(statement, mapping, left, param); storeVertexUniform(mapping.getRightVariable()); } } else { if (updateRightFromUniforms(param, mapping, fragmentDeclaredUniforms, statement)) { updateMaterialTextureType(statement, mapping, left, param); storeFragmentUniform(mapping.getRightVariable()); } } } else if (right.getNameSpace().equals(ShaderGenerator.NAME_SPACE_WORLD_PARAM)) { UniformBinding worldParam = findWorldParam(right.getName()); if (worldParam == null) { throw new MatParseException("Could not find a World Parameter named " + right.getName(), statement); } if (definition.getType() == ShaderType.Vertex) { if (updateRightFromUniforms(worldParam, mapping, vertexDeclaredUniforms)) { storeVertexUniform(mapping.getRightVariable()); } } else { if (updateRightFromUniforms(worldParam, mapping, fragmentDeclaredUniforms)) { storeFragmentUniform(mapping.getRightVariable()); } } } else { ShaderNode node = nodes.get(right.getNameSpace()); if (node == null) { throw new MatParseException("Undeclared node" + right.getNameSpace() + ". Make sure this node is declared before the current node", statement); } ShaderNodeVariable var = findNodeOutput(node.getDefinition().getOutputs(), right.getName()); if (var == null) { throw new MatParseException("Cannot find output variable" + right.getName() + " form ShaderNode " + node.getName(), statement); } right.setNameSpace(node.getName()); right.setType(var.getType()); right.setMultiplicity(var.getMultiplicity()); mapping.setRightVariable(right); storeVaryings(node, mapping.getRightVariable()); } checkTypes(mapping, statement); return mapping; } /** * Updates the material texture type of the variable mapping. * * @param statement the statement. * @param mapping the variable mapping. * @param left the left variable. * @param param the material parameter. * @throws MatParseException if the texture type isn't valid. */ private void updateMaterialTextureType(final Statement statement, final VariableMapping mapping, final ShaderNodeVariable left, final MatParam param) throws MatParseException { if (!mapping.getRightVariable().getType().contains("|")) { return; } final String type = fixSamplerType(left.getType(), mapping.getRightVariable().getType()); if (type != null) { mapping.getRightVariable().setType(type); } else { throw new MatParseException(param.getVarType().toString() + " can only be matched to one of " + param.getVarType().getGlslType().replaceAll("\\|", ",") + " found " + left.getType(), statement); } } /** * Reads an output mapping. * * @param statement the statement being read. * @return the mapping * @throws MatParseException if we have a problem with parsing the statement. */ public VariableMapping readOutputMapping(Statement statement) throws MatParseException { VariableMapping mapping; try { mapping = parseMapping(statement, OM_HAS_NAME_SPACE); } catch (final Exception e) { throw new MatParseException("Unexpected mapping format", statement, e); } final ShaderNodeDefinition definition = shaderNode.getDefinition(); final ShaderNodeVariable left = mapping.getLeftVariable(); final ShaderNodeVariable right = mapping.getRightVariable(); if (left.getType().startsWith("sampler") || right.getType().startsWith("sampler")) { throw new MatParseException("Samplers can only be inputs", statement); } if (left.getNameSpace().equals(ShaderGenerator.NAME_SPACE_GLOBAL)) { left.setType("vec4"); // Globals are all vec4 for now (maybe forever...) storeGlobal(left, statement); } else { throw new MatParseException("Only Global nameSpace is allowed for outputMapping, got" + left.getNameSpace(), statement); } if (!updateVariableFromList(right, definition.getOutputs())) { throw new MatParseException(right.getName() + " is not an output variable of " + definition.getName(), statement); } checkTypes(mapping, statement); return mapping; } /** * Reads a list of ShaderNodes * * @param statements the list of statements to read * @throws IOException if an I/O error occurs */ public void readNodes(List statements) throws IOException { if (techniqueDef.getShaderNodes() == null) { techniqueDef.setShaderNodes(new ArrayList()); techniqueDef.setShaderGenerationInfo(new ShaderGenerationInfo()); } for (Statement statement : statements) { String[] split = statement.getLine().split("[ \\{]"); if (statement.getLine().startsWith("ShaderNode ")) { String name = statement.getLine().substring("ShaderNode".length()).trim(); if (nodes == null) { nodes = new HashMap(); } if (!nodes.containsKey(name)) { shaderNode = new ShaderNode(); shaderNode.setName(name); techniqueDef.getShaderGenerationInfo().getUnusedNodes().add(name); readShaderNode(statement.getContents()); nodes.put(name, shaderNode); techniqueDef.getShaderNodes().add(shaderNode); } else { throw new MatParseException("ShaderNode " + name + " is already defined", statement); } } else { throw new MatParseException("ShaderNode", split[0], statement); } } } /** * retrieve the leftType corresponding sampler type from the rightType * * @param leftType the left samplerType * @param rightType the right sampler type (can be multiple types separated * by "|" * @return the type or null if not found */ public String fixSamplerType(String leftType, String rightType) { String[] types = rightType.split("\\|"); for (String string : types) { if (leftType.equals(string)) { return string; } } return null; } /** * Stores a global output. * * @param var the variable to store. * @param varStatement the statement being read. * @throws MatParseException if we have duplicates of a global vertex output variable. */ public void storeGlobal(ShaderNodeVariable var, Statement varStatement) throws MatParseException { var.setShaderOutput(true); final ShaderGenerationInfo generationInfo = techniqueDef.getShaderGenerationInfo(); final ShaderNodeDefinition definition = shaderNode.getDefinition(); if (definition.getType() == ShaderType.Vertex) { ShaderNodeVariable global = generationInfo.getVertexGlobal(); if (global != null) { if (!global.getName().equals(var.getName())) { throw new MatParseException("A global output is already defined for the vertex shader: " + global.getName() + ". vertex shader can only have one global output", varStatement); } } else { generationInfo.setVertexGlobal(var); } } else if (definition.getType() == ShaderType.Fragment) { storeVariable(var, generationInfo.getFragmentGlobals()); } } /** * Stores an attribute. * * @param var the variable to store. */ public void storeAttribute(ShaderNodeVariable var) { storeVariable(var, techniqueDef.getShaderGenerationInfo().getAttributes()); } /** * Stores a vertex uniform. * * @param var the variable to store. */ public void storeVertexUniform(ShaderNodeVariable var) { storeVariable(var, techniqueDef.getShaderGenerationInfo().getVertexUniforms()); } /** * store a fragment uniform * * @param var the variable to store */ public void storeFragmentUniform(ShaderNodeVariable var) { storeVariable(var, techniqueDef.getShaderGenerationInfo().getFragmentUniforms()); } /** * sets the assetManager * * @param assetManager for loading assets (alias created) */ public void setAssetManager(AssetManager assetManager) { this.assetManager = assetManager; } /** * Find the definition from this statement (loads it if necessary) * * @param statement the statement being read * @return the definition * @throws IOException if an I/O error occurs */ public ShaderNodeDefinition findDefinition(Statement statement) throws IOException { final String defLine[] = statement.getLine().split(":"); if (defLine.length != 3) { throw new MatParseException("Can't find shader node definition for: ", statement); } final Map nodeDefinitions = getNodeDefinitions(); final String definitionName = defLine[1].trim(); final String definitionPath = defLine[2].trim(); final String fullName = definitionName + ":" + definitionPath; ShaderNodeDefinition def = nodeDefinitions.get(fullName); if (def != null) { return def; } List defs; try { defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(definitionPath)); } catch (final AssetNotFoundException e) { throw new MatParseException("Couldn't find " + definitionPath, statement, e); } for (final ShaderNodeDefinition definition : defs) { if (definitionName.equals(definition.getName())) { def = definition; } final String key = definition.getName() + ":" + definitionPath; if (!(nodeDefinitions.containsKey(key))) { nodeDefinitions.put(key, definition); } } if (def == null) { throw new MatParseException(definitionName + " is not a declared as Shader Node Definition", statement); } return def; } /** * store a varying * * @param node the shaderNode * @param variable the variable to store */ public void storeVaryings(ShaderNode node, ShaderNodeVariable variable) { variable.setShaderOutput(true); final ShaderNodeDefinition nodeDefinition = node.getDefinition(); final ShaderNodeDefinition currentDefinition = shaderNode.getDefinition(); if (nodeDefinition.getType() != ShaderType.Vertex || currentDefinition.getType() != ShaderType.Fragment) { return; } final String fullName = node.getName() + "." + variable.getName(); DeclaredVariable declaredVar = varyings.get(fullName); if (declaredVar == null) { techniqueDef.getShaderGenerationInfo().getVaryings().add(variable); declaredVar = new DeclaredVariable(variable); varyings.put(fullName, declaredVar); } declaredVar.addNode(shaderNode); // If a variable is declared with the same name as an input and an output and is a varying, // set it as a shader output, so it's declared as a varying only once. for (final VariableMapping variableMapping : node.getInputMapping()) { final ShaderNodeVariable leftVariable = variableMapping.getLeftVariable(); if (leftVariable.getName().equals(variable.getName())) { leftVariable.setShaderOutput(true); } } } /** * Merges 2 conditions with the given operator * * @param condition1 the first condition * @param condition2 the second condition * @param operator the operator {@literal ("&&" or "||&)} * @return the merged condition */ public 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; } } /** * Searches a variable in a list from its name and merges the conditions of the * variables. * * @param variable the variable. * @param varList the variable list. */ public void storeVariable(ShaderNodeVariable variable, List varList) { for (ShaderNodeVariable var : varList) { if (var.getName().equals(variable.getName())) { return; } } varList.add(variable); } /** * check the types of a mapping, left type must match right type take the * swizzle into account * * @param mapping the mapping * @param statement1 the statement being read * @throws MatParseException in case of a syntax error */ protected void checkTypes(VariableMapping mapping, Statement statement1) throws MatParseException { if (!ShaderUtils.typesMatch(mapping)) { String ls = mapping.getLeftSwizzling().length() == 0 ? "" : "." + mapping.getLeftSwizzling(); String rs = mapping.getRightSwizzling().length() == 0 ? "" : "." + mapping.getRightSwizzling(); throw new MatParseException("Type mismatch, cannot convert " + mapping.getRightVariable().getType() + rs + " to " + mapping.getLeftVariable().getType() + ls, statement1); } if (!ShaderUtils.multiplicityMatch(mapping)) { String type1 = mapping.getLeftVariable().getType() + "[" + mapping.getLeftVariable().getMultiplicity() + "]"; String type2 = mapping.getRightVariable().getType() + "[" + mapping.getRightVariable().getMultiplicity() + "]"; throw new MatParseException("Type mismatch, cannot convert " + type1 + " to " + type2, statement1); } } private Map getNodeDefinitions() { if (nodeDefinitions == null) { nodeDefinitions = new HashMap<>(); } return nodeDefinitions; } public void clear() { nodeDefinitions.clear(); nodes.clear(); shaderNodeDefinition = null; shaderNode = null; techniqueDef = null; attributes.clear(); vertexDeclaredUniforms.clear(); fragmentDeclaredUniforms.clear(); varyings.clear(); materialDef = null; shaderLanguage = ""; shaderName = ""; varNames.clear(); assetManager = null; nulledConditions.clear(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy