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

com.jme3.material.TechniqueDef Maven / Gradle / Ivy

There is a newer version: 3.7.0-stable
Show 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;

import com.jme3.asset.AssetManager;
import com.jme3.export.*;
import com.jme3.material.logic.TechniqueDefLogic;
import com.jme3.renderer.Caps;
import com.jme3.shader.*;
import com.jme3.shader.Shader.ShaderType;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * Describes a technique definition.
 *
 * @author Kirill Vainer
 */
public class TechniqueDef implements Savable, Cloneable {

    /**
     * Version #1: Separate shader language for each shader source.
     */
    public static final int SAVABLE_VERSION = 1;

    /**
     * The default technique name.
     *
     * The technique with this name is selected if no specific technique is
     * requested by the user. Currently set to "Default".
     */
    public static final String DEFAULT_TECHNIQUE_NAME = "Default";

    /**
     * Describes light rendering mode.
     */
    public enum LightMode {
        /**
         * Disable light-based rendering
         */
        Disable,

        /**
         * Enable light rendering by using a single pass.
         * 

* An array of light positions and light colors is passed to the shader * containing the world light list for the geometry being rendered. */ SinglePass, /** * Enable light rendering by using multi-pass rendering. *

* The geometry will be rendered once for each light. Each time the * light position and light color uniforms are updated to contain * the values for the current light. The ambient light color uniform * is only set to the ambient light color on the first pass, future * passes have it set to black. */ MultiPass, /** * Enable light rendering by using a single pass, and also uses Image based lighting for global lighting * Usually used for PBR *

* An array of light positions and light colors is passed to the shader * containing the world light list for the geometry being rendered. * Light probes are also passed to the shader. */ SinglePassAndImageBased, /** * @deprecated OpenGL1 is not supported anymore */ @Deprecated FixedPipeline, /** * Similar to {@link #SinglePass} except the type of each light is known * at shader compile time. *

* The advantage is that the shader can be much more efficient, i.e. not * do operations required for spot and point lights if it knows the * light is a directional light. The disadvantage is that the number of * shaders used balloons because of the variations in the number of * lights used by objects. */ StaticPass } public enum ShadowMode { Disable, InPass, PostPass, } /** * Define in what space the light data should be sent to the shader. */ public enum LightSpace { World, View, Legacy } private final EnumSet requiredCaps = EnumSet.noneOf(Caps.class); private String name; private int sortId; private EnumMap shaderLanguages; private EnumMap shaderNames; private String shaderPrologue; private ArrayList defineNames; private ArrayList defineTypes; private HashMap paramToDefineId; private final HashMap definesToShaderMap; private boolean usesNodes = false; private List shaderNodes; private ShaderGenerationInfo shaderGenerationInfo; private boolean noRender = false; private RenderState renderState; private RenderState forcedRenderState; private LightMode lightMode = LightMode.Disable; private ShadowMode shadowMode = ShadowMode.Disable; private TechniqueDefLogic logic; private ArrayList worldBinds; //The space in which the light should be transposed before sending to the shader. private LightSpace lightSpace; //used to find the best fit technique private float weight = 0; /** * Creates a new technique definition. *

* Used internally by the J3M/J3MD loader. * * @param name The name of the technique * @param sortId a unique ID for sorting */ public TechniqueDef(String name, int sortId) { this(); this.sortId = sortId; this.name = name; } /** * Serialization only. Do not use. */ protected TechniqueDef() { shaderLanguages = new EnumMap(Shader.ShaderType.class); shaderNames = new EnumMap(Shader.ShaderType.class); defineNames = new ArrayList(); defineTypes = new ArrayList(); paramToDefineId = new HashMap(); definesToShaderMap = new HashMap(); worldBinds = new ArrayList<>(); } /** * @return A unique sort ID. * No other technique definition can have the same ID. */ public int getSortId() { return sortId; } /** * Returns the name of this technique as specified in the J3MD file. * Default * techniques have the name {@link #DEFAULT_TECHNIQUE_NAME}. * * @return the name of this technique */ public String getName() { return name; } /** * Returns the light mode. * @return the light mode. * @see LightMode */ public LightMode getLightMode() { return lightMode; } /** * Set the light mode * * @param lightMode the light mode * * @see LightMode */ public void setLightMode(LightMode lightMode) { this.lightMode = lightMode; //if light space is not specified we set it to Legacy if (lightSpace == null) { if (lightMode== LightMode.MultiPass) { lightSpace = LightSpace.Legacy; } else { lightSpace = LightSpace.World; } } } public void setLogic(TechniqueDefLogic logic) { this.logic = logic; } public TechniqueDefLogic getLogic() { return logic; } /** * Returns the shadow mode. * @return the shadow mode. */ public ShadowMode getShadowMode() { return shadowMode; } /** * Set the shadow mode. * * @param shadowMode the shadow mode. * * @see ShadowMode */ public void setShadowMode(ShadowMode shadowMode) { this.shadowMode = shadowMode; } /** * Returns the render state that this technique is using * @return the render state that this technique is using * @see #setRenderState(com.jme3.material.RenderState) */ public RenderState getRenderState() { return renderState; } /** * Sets the render state that this technique is using. * * @param renderState the render state that this technique is using. * * @see RenderState */ public void setRenderState(RenderState renderState) { this.renderState = renderState; } /** * Sets if this technique should not be used to render. * * @param noRender not render or render ? * * @see #isNoRender() */ public void setNoRender(boolean noRender) { this.noRender = noRender; } /** * Returns true if this technique should not be used to render. * (e.g. to not render a material with default technique) * * @return true if this technique should not be rendered, false otherwise. * */ public boolean isNoRender() { return noRender; } /** * Returns true if this technique uses Shader Nodes, false otherwise. * * @return true if this technique uses Shader Nodes, false otherwise. * */ public boolean isUsingShaderNodes() { return usesNodes; } /** * Gets the {@link Caps renderer capabilities} that are required * by this technique. * * @return the required renderer capabilities */ public EnumSet getRequiredCaps() { return requiredCaps; } /** * Sets the shaders that this technique definition will use. * * @param vertexShader The name of the vertex shader * @param fragmentShader The name of the fragment shader * @param vertLanguage The vertex shader language * @param fragLanguage The fragment shader language */ public void setShaderFile(String vertexShader, String fragmentShader, String vertLanguage, String fragLanguage) { this.shaderLanguages.put(Shader.ShaderType.Vertex, vertLanguage); this.shaderNames.put(Shader.ShaderType.Vertex, vertexShader); this.shaderLanguages.put(Shader.ShaderType.Fragment, fragLanguage); this.shaderNames.put(Shader.ShaderType.Fragment, fragmentShader); requiredCaps.clear(); Caps vertCap = Caps.valueOf(vertLanguage); requiredCaps.add(vertCap); Caps fragCap = Caps.valueOf(fragLanguage); requiredCaps.add(fragCap); weight = Math.max(vertCap.ordinal(), fragCap.ordinal()); } /** * Set a string which is prepended to every shader used by this technique. * * Typically this is used for preset defines. * * @param shaderPrologue The prologue to append before the technique's shaders. */ public void setShaderPrologue(String shaderPrologue) { this.shaderPrologue = shaderPrologue; } /** * @return the shader prologue which is prepended to every shader. */ public String getShaderPrologue() { return shaderPrologue; } /** * Returns the define name which the given material parameter influences. * * @param paramName The parameter name to look up * @return The define name * * @see #addShaderParamDefine(java.lang.String, com.jme3.shader.VarType, java.lang.String) */ public String getShaderParamDefine(String paramName) { Integer defineId = paramToDefineId.get(paramName); if (defineId != null) { return defineNames.get(defineId); } else { return null; } } /** * Get the define ID for a given material parameter. * * @param paramName The parameter name to look up * @return The define ID, or null if not found. */ public Integer getShaderParamDefineId(String paramName) { return paramToDefineId.get(paramName); } /** * Get the type of a particular define. * * @param defineId The define ID to lookup. * @return The type of the define, or null if not found. */ public VarType getDefineIdType(int defineId) { return defineId < defineTypes.size() ? defineTypes.get(defineId) : null; } /** * Adds a define linked to a material parameter. *

* Any time the material parameter on the parent material is altered, * the appropriate define on the technique will be modified as well. * When set, the material parameter will be mapped to an integer define, * typically 1 if it is set, unless it is an integer or a float, * in which case it will be converted into an integer. * * @param paramName The name of the material parameter to link to. * @param paramType The type of the material parameter to link to. * @param defineName The name of the define parameter, e.g. USE_LIGHTING */ public void addShaderParamDefine(String paramName, VarType paramType, String defineName) { int defineId = defineNames.size(); if (defineId >= DefineList.MAX_DEFINES) { throw new IllegalStateException("Cannot have more than " + DefineList.MAX_DEFINES + " defines on a technique."); } paramToDefineId.put(paramName, defineId); defineNames.add(defineName); defineTypes.add(paramType); } /** * Add an unmapped define which can only be set by define ID. * * Unmapped defines are used by technique renderers to * configure the shader internally before rendering. * * @param defineName The define name to create * @param defineType the type for the new define * @return The define ID of the created define */ public int addShaderUnmappedDefine(String defineName, VarType defineType) { int defineId = defineNames.size(); if (defineId >= DefineList.MAX_DEFINES) { throw new IllegalStateException("Cannot have more than " + DefineList.MAX_DEFINES + " defines on a technique."); } defineNames.add(defineName); defineTypes.add(defineType); return defineId; } /** * Get the names of all defines declared on this technique definition. * * The defines are returned in order of declaration. * * @return the names of all defines declared. */ public String[] getDefineNames() { return defineNames.toArray(new String[0]); } /** * Get the types of all defines declared on this technique definition. * * The types are returned in order of declaration. * * @return the types of all defines declared. */ public VarType[] getDefineTypes() { return defineTypes.toArray(new VarType[0]); } /** * Create a define list with the size matching the number * of defines on this technique. * * @return a define list with the size matching the number * of defines on this technique. */ public DefineList createDefineList() { return new DefineList(defineNames.size()); } private Shader loadShader(AssetManager assetManager, EnumSet rendererCaps, DefineList defines) { StringBuilder sb = new StringBuilder(); sb.append(shaderPrologue); defines.generateSource(sb, defineNames, defineTypes); String definesSourceCode = sb.toString(); Shader shader; if (isUsingShaderNodes()) { ShaderGenerator shaderGenerator = assetManager.getShaderGenerator(rendererCaps); if (shaderGenerator == null) { throw new UnsupportedOperationException("ShaderGenerator was not initialized, " + "make sure assetManager.getGenerator(caps) has been called"); } shaderGenerator.initialize(this); shader = shaderGenerator.generateShader(definesSourceCode); } else { shader = new Shader(); for (ShaderType type : ShaderType.values()) { String language = shaderLanguages.get(type); String shaderSourceAssetName = shaderNames.get(type); if (language == null || shaderSourceAssetName == null) { continue; } String shaderSourceCode = (String) assetManager.loadAsset(shaderSourceAssetName); shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language); } } for (final UniformBinding binding : getWorldBindings()) { shader.addUniformBinding(binding); } return shader; } public Shader getShader(AssetManager assetManager, EnumSet rendererCaps, DefineList defines) { Shader shader = definesToShaderMap.get(defines); if (shader == null) { shader = loadShader(assetManager, rendererCaps, defines); definesToShaderMap.put(defines.deepClone(), shader); } return shader; } /** * Sets the shaders that this technique definition will use. * * @param shaderNames EnumMap containing all shader names for this stage * @param shaderLanguages EnumMap containing all shader languages for this stage */ public void setShaderFile(EnumMap shaderNames, EnumMap shaderLanguages) { requiredCaps.clear(); weight = 0; for (Shader.ShaderType shaderType : shaderNames.keySet()) { String language = shaderLanguages.get(shaderType); String shaderFile = shaderNames.get(shaderType); this.shaderLanguages.put(shaderType, language); this.shaderNames.put(shaderType, shaderFile); Caps cap = Caps.valueOf(language); requiredCaps.add(cap); weight = Math.max(weight, cap.ordinal()); if (shaderType.equals(Shader.ShaderType.Geometry)) { requiredCaps.add(Caps.GeometryShader); } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) { requiredCaps.add(Caps.TesselationShader); } } } /** * Returns the name of the fragment shader used by the technique, or null * if no fragment shader is specified. * * @return the name of the fragment shader to be used. */ public String getFragmentShaderName() { return shaderNames.get(Shader.ShaderType.Fragment); } /** * Returns the name of the vertex shader used by the technique, or null * if no vertex shader is specified. * * @return the name of the vertex shader to be used. */ public String getVertexShaderName() { return shaderNames.get(Shader.ShaderType.Vertex); } /** * Returns the language of the fragment shader used in this technique. * * @return the name of the language (such as "GLSL100") */ public String getFragmentShaderLanguage() { return shaderLanguages.get(Shader.ShaderType.Fragment); } /** * Returns the language of the vertex shader used in this technique. * * @return the name of the language (such as "GLSL100") */ public String getVertexShaderLanguage() { return shaderLanguages.get(Shader.ShaderType.Vertex); } /**Returns the language for each shader program * @param shaderType Fragment/Vertex/etcetera * @return the name of the language */ public String getShaderProgramLanguage(Shader.ShaderType shaderType) { return shaderLanguages.get(shaderType); } /**Returns the name for each shader program * @param shaderType Fragment/Vertex/etcetera * @return the name of the program */ public String getShaderProgramName(Shader.ShaderType shaderType) { return shaderNames.get(shaderType); } /** * returns the weight of the technique def * * @return the weight */ public float getWeight() { return weight; } /** * Adds a new world parameter by the given name. * * @param name The world parameter to add. * @return True if the world parameter name was found and added * to the list of world parameters, false otherwise. */ public boolean addWorldParam(String name) { try { worldBinds.add(UniformBinding.valueOf(name)); return true; } catch (IllegalArgumentException ex) { return false; } } public RenderState getForcedRenderState() { return forcedRenderState; } public void setForcedRenderState(RenderState forcedRenderState) { this.forcedRenderState = forcedRenderState; } /** * Returns a list of world parameters that are used by this * technique definition. * * @return The list of world parameters */ public List getWorldBindings() { return worldBinds; } @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", null); oc.write(shaderNames.get(Shader.ShaderType.Vertex), "vertName", null); oc.write(shaderNames.get(Shader.ShaderType.Fragment), "fragName", null); oc.write(shaderNames.get(Shader.ShaderType.Geometry), "geomName", null); oc.write(shaderNames.get(Shader.ShaderType.TessellationControl), "tsctrlName", null); oc.write(shaderNames.get(Shader.ShaderType.TessellationEvaluation), "tsevalName", null); oc.write(shaderLanguages.get(Shader.ShaderType.Vertex), "vertLanguage", null); oc.write(shaderLanguages.get(Shader.ShaderType.Fragment), "fragLanguage", null); oc.write(shaderLanguages.get(Shader.ShaderType.Geometry), "geomLanguage", null); oc.write(shaderLanguages.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null); oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null); oc.write(shaderPrologue, "shaderPrologue", null); oc.write(lightMode, "lightMode", LightMode.Disable); oc.write(shadowMode, "shadowMode", ShadowMode.Disable); oc.write(renderState, "renderState", null); oc.write(noRender, "noRender", false); oc.write(usesNodes, "usesNodes", false); oc.writeSavableArrayList((ArrayList)shaderNodes,"shaderNodes", null); oc.write(shaderGenerationInfo, "shaderGenerationInfo", null); // TODO: Finish this when Map export is available // oc.write(defineParams, "defineParams", null); // TODO: Finish this when List export is available // oc.write(worldBinds, "worldBinds", null); } @Override @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); name = ic.readString("name", null); shaderNames.put(Shader.ShaderType.Vertex,ic.readString("vertName", null)); shaderNames.put(Shader.ShaderType.Fragment,ic.readString("fragName", null)); shaderNames.put(Shader.ShaderType.Geometry,ic.readString("geomName", null)); shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null)); shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null)); shaderPrologue = ic.readString("shaderPrologue", null); lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable); shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable); renderState = (RenderState) ic.readSavable("renderState", null); noRender = ic.readBoolean("noRender", false); if (ic.getSavableVersion(TechniqueDef.class) == 0) { // Old version shaderLanguages.put(Shader.ShaderType.Vertex,ic.readString("shaderLang", null)); shaderLanguages.put(Shader.ShaderType.Fragment,shaderLanguages.get(Shader.ShaderType.Vertex)); } else { // New version shaderLanguages.put(Shader.ShaderType.Vertex,ic.readString("vertLanguage", null)); shaderLanguages.put(Shader.ShaderType.Fragment,ic.readString("fragLanguage", null)); shaderLanguages.put(Shader.ShaderType.Geometry,ic.readString("geomLanguage", null)); shaderLanguages.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlLanguage", null)); shaderLanguages.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalLanguage", null)); } usesNodes = ic.readBoolean("usesNodes", false); shaderNodes = ic.readSavableArrayList("shaderNodes", null); shaderGenerationInfo = (ShaderGenerationInfo) ic.readSavable("shaderGenerationInfo", null); } public List getShaderNodes() { return shaderNodes; } public void setShaderNodes(List shaderNodes) { this.shaderNodes = shaderNodes; usesNodes = true; } /** * Returns the Enum containing the ShaderProgramNames; * @return the pre-existing EnumMap */ public EnumMap getShaderProgramNames() { return shaderNames; } /** * Returns the Enum containing the ShaderProgramLanguages; * @return the pre-existing EnumMap */ public EnumMap getShaderProgramLanguages() { return shaderLanguages; } public ShaderGenerationInfo getShaderGenerationInfo() { return shaderGenerationInfo; } public void setShaderGenerationInfo(ShaderGenerationInfo shaderGenerationInfo) { this.shaderGenerationInfo = shaderGenerationInfo; } @Override public String toString() { return "TechniqueDef[name=" + name + ", requiredCaps=" + requiredCaps + ", noRender=" + noRender + ", lightMode=" + lightMode + ", usesNodes=" + usesNodes + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + "]"; } /** * Returns the space in which the light data should be passed to the shader. * @return the light space */ public LightSpace getLightSpace() { return lightSpace; } /** * Sets the space in which the light data should be passed to the shader. * @param lightSpace the light space */ public void setLightSpace(LightSpace lightSpace) { this.lightSpace = lightSpace; } @Override public TechniqueDef clone() throws CloneNotSupportedException { //cannot use super.clone because of the final fields instance that would be shared by the clones. TechniqueDef clone = new TechniqueDef(name, sortId); clone.noRender = noRender; clone.lightMode = lightMode; clone.shadowMode = shadowMode; clone.lightSpace = lightSpace; clone.usesNodes = usesNodes; clone.shaderPrologue = shaderPrologue; clone.setShaderFile(shaderNames, shaderLanguages); clone.defineNames = new ArrayList<>(defineNames.size()); clone.defineNames.addAll(defineNames); clone.defineTypes = new ArrayList<>(defineTypes.size()); clone.defineTypes.addAll(defineTypes); clone.paramToDefineId = new HashMap<>(paramToDefineId.size()); clone.paramToDefineId.putAll(paramToDefineId); if (shaderNodes != null) { clone.shaderNodes = new ArrayList<>(); for (ShaderNode shaderNode : shaderNodes) { clone.shaderNodes.add(shaderNode.clone()); } clone.shaderGenerationInfo = shaderGenerationInfo.clone(); } if (renderState != null) { clone.setRenderState(renderState.clone()); } if (forcedRenderState != null) { clone.setForcedRenderState(forcedRenderState.clone()); } try { clone.logic = logic.getClass().getConstructor(TechniqueDef.class).newInstance(clone); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } clone.worldBinds = new ArrayList<>(worldBinds.size()); clone.worldBinds.addAll(worldBinds); return clone; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy