com.jme3.material.TechniqueDef Maven / Gradle / Ivy
Show all versions of jme3-core Show documentation
/*
* 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;
}
}