From 723e3c0e30e82ac25fde139c8ce217c5d575c3cb Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Thu, 21 Mar 2013 19:09:52 +0000 Subject: [PATCH] Commit patch from abies that drastically reduce the garbage creation when switching techniques. It also reduce grabage collection for the AbdtractShadowRenderer. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10497 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../src/core/com/jme3/material/Technique.java | 521 ++++--- .../src/core/com/jme3/shader/DefineList.java | 466 +++--- .../jme3/shadow/AbstractShadowRenderer.java | 1257 +++++++++-------- 3 files changed, 1155 insertions(+), 1089 deletions(-) diff --git a/engine/src/core/com/jme3/material/Technique.java b/engine/src/core/com/jme3/material/Technique.java index d08e2f64b..ad9f76ec7 100644 --- a/engine/src/core/com/jme3/material/Technique.java +++ b/engine/src/core/com/jme3/material/Technique.java @@ -1,261 +1,260 @@ -/* - * Copyright (c) 2009-2012 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.renderer.Caps; -import com.jme3.shader.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.logging.Logger; - -/** - * Represents a technique instance. - */ -public class Technique /* implements Savable */ { - - private static final Logger logger = Logger.getLogger(Technique.class.getName()); - private TechniqueDef def; - private Material owner; - private ArrayList worldBindUniforms; - private DefineList defines; - private Shader shader; - private boolean needReload = true; - - /** - * Creates a new technique instance that implements the given - * technique definition. - * - * @param owner The material that will own this technique - * @param def The technique definition being implemented. - */ - public Technique(Material owner, TechniqueDef def) { - this.owner = owner; - this.def = def; - if (def.isUsingShaders()) { - this.worldBindUniforms = new ArrayList(); - this.defines = new DefineList(); - } - } - - /** - * Serialization only. Do not use. - */ - public Technique() { - } - - /** - * Returns the technique definition that is implemented by this technique - * instance. - * - * @return the technique definition that is implemented by this technique - * instance. - */ - public TechniqueDef getDef() { - return def; - } - - /** - * Returns the shader currently used by this technique instance. - *

- * Shaders are typically loaded dynamically when the technique is first - * used, therefore, this variable will most likely be null most of the time. - * - * @return the shader currently used by this technique instance. - */ - public Shader getShader() { - return shader; - } - - /** - * Returns a list of uniforms that implements the world parameters - * that were requested by the material definition. - * - * @return a list of uniforms implementing the world parameters. - */ - public List getWorldBindUniforms() { - return worldBindUniforms; - } - - /** - * Called by the material to tell the technique a parameter was modified. - * Specify null for value if the param is to be cleared. - */ - void notifyParamChanged(String paramName, VarType type, Object value) { - // Check if there's a define binding associated with this - // parameter. - String defineName = def.getShaderParamDefine(paramName); - if (defineName != null) { - // There is a define. Change it on the define list. - // The "needReload" variable will determine - // if the shader will be reloaded when the material - // is rendered. - - if (value == null) { - // Clear the define. - needReload = defines.remove(defineName) || needReload; - } else { - // Set the define. - needReload = defines.set(defineName, type, value) || needReload; - } - } - } - - void updateUniformParam(String paramName, VarType type, Object value) { - if (paramName == null) { - throw new IllegalArgumentException(); - } - - Uniform u = shader.getUniform(paramName); - switch (type) { - case TextureBuffer: - case Texture2D: // fall intentional - case Texture3D: - case TextureArray: - case TextureCubeMap: - case Int: - u.setValue(VarType.Int, value); - break; - default: - u.setValue(type, value); - break; - } - } - - /** - * Returns true if the technique must be reloaded. - *

- * If a technique needs to reload, then the {@link Material} should - * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this - * technique. - * - * @return true if the technique must be reloaded. - */ - public boolean isNeedReload() { - return needReload; - } - - /** - * Prepares the technique for use by loading the shader and setting - * the proper defines based on material parameters. - * - * @param assetManager The asset manager to use for loading shaders. - */ - public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet rendererCaps) { - if (!def.isUsingShaders()) { - // No shaders are used, no processing is neccessary. - return; - } - - if (techniqueSwitched) { - // If the technique was switched, check if the define list changed - // based on material parameters. - DefineList newDefines = new DefineList(); - Collection params = owner.getParams(); - for (MatParam param : params) { - String defineName = def.getShaderParamDefine(param.getName()); - if (defineName != null) { - newDefines.set(defineName, param.getVarType(), param.getValue()); - } - } - - if (!defines.getCompiled().equals(newDefines.getCompiled())) { - // Defines were changed, update define list - defines.clear(); - defines.addFrom(newDefines); - needReload = true; - } - } - - if (needReload) { - loadShader(assetManager,rendererCaps); - } - } - - private void loadShader(AssetManager manager,EnumSet rendererCaps) { - - if (getDef().isUsingShaderNodes()) { - shader = manager.getShaderGenerator(rendererCaps).generateShader(this); - } else { - ShaderKey key = new ShaderKey(def.getVertexShaderName(), - def.getFragmentShaderName(), - getAllDefines(), - def.getVertexShaderLanguage(), - def.getFragmentShaderLanguage()); - shader = manager.loadShader(key); - - } - // register the world bound uniforms - worldBindUniforms.clear(); - if (def.getWorldBindings() != null) { - for (UniformBinding binding : def.getWorldBindings()) { - Uniform uniform = shader.getUniform("g_" + binding.name()); - uniform.setBinding(binding); - if (uniform != null) { - worldBindUniforms.add(uniform); - } - } - } - needReload = false; - } - - /** - * Computes the define list - * @return the complete define list - */ - public DefineList getAllDefines() { - DefineList allDefines = new DefineList(); - allDefines.addFrom(def.getShaderPresetDefines()); - allDefines.addFrom(defines); - return allDefines; - } - - /* - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = ex.getCapsule(this); - oc.write(def, "def", null); - oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null); - oc.write(defines, "defines", null); - oc.write(shader, "shader", null); - } - - public void read(JmeImporter im) throws IOException { - InputCapsule ic = im.getCapsule(this); - def = (TechniqueDef) ic.readSavable("def", null); - worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null); - defines = (DefineList) ic.readSavable("defines", null); - shader = (Shader) ic.readSavable("shader", null); - } - */ -} +/* + * Copyright (c) 2009-2012 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.renderer.Caps; +import com.jme3.shader.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Logger; + +/** + * Represents a technique instance. + */ +public class Technique /* implements Savable */ { + + private static final Logger logger = Logger.getLogger(Technique.class.getName()); + private TechniqueDef def; + private Material owner; + private ArrayList worldBindUniforms; + private DefineList defines; + private Shader shader; + private boolean needReload = true; + + /** + * Creates a new technique instance that implements the given + * technique definition. + * + * @param owner The material that will own this technique + * @param def The technique definition being implemented. + */ + public Technique(Material owner, TechniqueDef def) { + this.owner = owner; + this.def = def; + if (def.isUsingShaders()) { + this.worldBindUniforms = new ArrayList(); + this.defines = new DefineList(); + } + } + + /** + * Serialization only. Do not use. + */ + public Technique() { + } + + /** + * Returns the technique definition that is implemented by this technique + * instance. + * + * @return the technique definition that is implemented by this technique + * instance. + */ + public TechniqueDef getDef() { + return def; + } + + /** + * Returns the shader currently used by this technique instance. + *

+ * Shaders are typically loaded dynamically when the technique is first + * used, therefore, this variable will most likely be null most of the time. + * + * @return the shader currently used by this technique instance. + */ + public Shader getShader() { + return shader; + } + + /** + * Returns a list of uniforms that implements the world parameters + * that were requested by the material definition. + * + * @return a list of uniforms implementing the world parameters. + */ + public List getWorldBindUniforms() { + return worldBindUniforms; + } + + /** + * Called by the material to tell the technique a parameter was modified. + * Specify null for value if the param is to be cleared. + */ + void notifyParamChanged(String paramName, VarType type, Object value) { + // Check if there's a define binding associated with this + // parameter. + String defineName = def.getShaderParamDefine(paramName); + if (defineName != null) { + // There is a define. Change it on the define list. + // The "needReload" variable will determine + // if the shader will be reloaded when the material + // is rendered. + + if (value == null) { + // Clear the define. + needReload = defines.remove(defineName) || needReload; + } else { + // Set the define. + needReload = defines.set(defineName, type, value) || needReload; + } + } + } + + void updateUniformParam(String paramName, VarType type, Object value) { + if (paramName == null) { + throw new IllegalArgumentException(); + } + + Uniform u = shader.getUniform(paramName); + switch (type) { + case TextureBuffer: + case Texture2D: // fall intentional + case Texture3D: + case TextureArray: + case TextureCubeMap: + case Int: + u.setValue(VarType.Int, value); + break; + default: + u.setValue(type, value); + break; + } + } + + /** + * Returns true if the technique must be reloaded. + *

+ * If a technique needs to reload, then the {@link Material} should + * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this + * technique. + * + * @return true if the technique must be reloaded. + */ + public boolean isNeedReload() { + return needReload; + } + + /** + * Prepares the technique for use by loading the shader and setting + * the proper defines based on material parameters. + * + * @param assetManager The asset manager to use for loading shaders. + */ + public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet rendererCaps) { + if (!def.isUsingShaders()) { + // No shaders are used, no processing is neccessary. + return; + } + + if (techniqueSwitched) { + // If the technique was switched, check if the define list changed + // based on material parameters. + + Collection params = owner.getParams(); + + if (!defines.equalsParams(params,def)) { + // Defines were changed, update define list + defines.clear(); + for (MatParam param : params) { + String defineName = def.getShaderParamDefine(param.getName()); + if (defineName != null) { + defines.set(defineName, param.getVarType(), param.getValue()); + } + } + needReload = true; + } + } + + if (needReload) { + loadShader(assetManager,rendererCaps); + } + } + + private void loadShader(AssetManager manager,EnumSet rendererCaps) { + + if (getDef().isUsingShaderNodes()) { + shader = manager.getShaderGenerator(rendererCaps).generateShader(this); + } else { + ShaderKey key = new ShaderKey(def.getVertexShaderName(), + def.getFragmentShaderName(), + getAllDefines(), + def.getVertexShaderLanguage(), + def.getFragmentShaderLanguage()); + shader = manager.loadShader(key); + + } + // register the world bound uniforms + worldBindUniforms.clear(); + if (def.getWorldBindings() != null) { + for (UniformBinding binding : def.getWorldBindings()) { + Uniform uniform = shader.getUniform("g_" + binding.name()); + uniform.setBinding(binding); + if (uniform != null) { + worldBindUniforms.add(uniform); + } + } + } + needReload = false; + } + + /** + * Computes the define list + * @return the complete define list + */ + public DefineList getAllDefines() { + DefineList allDefines = new DefineList(); + allDefines.addFrom(def.getShaderPresetDefines()); + allDefines.addFrom(defines); + return allDefines; + } + + /* + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(def, "def", null); + oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null); + oc.write(defines, "defines", null); + oc.write(shader, "shader", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + def = (TechniqueDef) ic.readSavable("def", null); + worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null); + defines = (DefineList) ic.readSavable("defines", null); + shader = (Shader) ic.readSavable("shader", null); + } + */ +} diff --git a/engine/src/core/com/jme3/shader/DefineList.java b/engine/src/core/com/jme3/shader/DefineList.java index 9c5fd2777..511cd03b1 100644 --- a/engine/src/core/com/jme3/shader/DefineList.java +++ b/engine/src/core/com/jme3/shader/DefineList.java @@ -1,203 +1,263 @@ -/* - * Copyright (c) 2009-2012 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.shader; - -import com.jme3.export.*; -import java.io.IOException; -import java.util.Map; -import java.util.TreeMap; - -public class DefineList implements Savable, Cloneable { - - private static final String ONE = "1"; - - private TreeMap defines = new TreeMap(); - private String compiled = null; - private int cachedHashCode = 0; - - public void write(JmeExporter ex) throws IOException{ - OutputCapsule oc = ex.getCapsule(this); - - String[] keys = new String[defines.size()]; - String[] vals = new String[defines.size()]; - - int i = 0; - for (Map.Entry define : defines.entrySet()){ - keys[i] = define.getKey(); - vals[i] = define.getValue(); - i++; - } - - oc.write(keys, "keys", null); - oc.write(vals, "vals", null); - } - - public void read(JmeImporter im) throws IOException{ - InputCapsule ic = im.getCapsule(this); - - String[] keys = ic.readStringArray("keys", null); - String[] vals = ic.readStringArray("vals", null); - for (int i = 0; i < keys.length; i++){ - defines.put(keys[i], vals[i]); - } - } - - public void clear() { - defines.clear(); - compiled = ""; - cachedHashCode = 0; - } - - public String get(String key){ - return defines.get(key); - } - - @Override - public DefineList clone() { - try { - DefineList clone = (DefineList) super.clone(); - clone.cachedHashCode = 0; - clone.compiled = null; - clone.defines = (TreeMap) defines.clone(); - return clone; - } catch (CloneNotSupportedException ex) { - throw new AssertionError(); - } - } - - public boolean set(String key, VarType type, Object val){ - if (val == null){ - defines.remove(key); - compiled = null; - cachedHashCode = 0; - return true; - } - - switch (type){ - case Boolean: - if (((Boolean) val).booleanValue()) { - // same literal, != will work - if (defines.put(key, ONE) != ONE) { - compiled = null; - cachedHashCode = 0; - return true; - } - } else if (defines.containsKey(key)) { - defines.remove(key); - compiled = null; - cachedHashCode = 0; - return true; - } - - break; - case Float: - case Int: - String newValue = val.toString(); - String original = defines.put(key, newValue); - if (!val.equals(original)) { - compiled = null; - cachedHashCode = 0; - return true; - } - break; - default: - // same literal, != will work - if (defines.put(key, ONE) != ONE) { - compiled = null; - cachedHashCode = 0; - return true; - } - break; - } - - return false; - } - - public boolean remove(String key){ - if (defines.remove(key) != null) { - compiled = null; - cachedHashCode = 0; - return true; - } - return false; - } - - public void addFrom(DefineList other){ - if (other == null) { - return; - } - compiled = null; - cachedHashCode = 0; - defines.putAll(other.defines); - } - - public String getCompiled(){ - if (compiled == null){ - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : defines.entrySet()){ - sb.append("#define ").append(entry.getKey()).append(" "); - sb.append(entry.getValue()).append('\n'); - } - compiled = sb.toString(); - } - return compiled; - } - - @Override - public boolean equals(Object obj) { - final DefineList other = (DefineList) obj; - return defines.equals(other.defines); - } - - @Override - public int hashCode() { - if (cachedHashCode == 0) { - cachedHashCode = defines.hashCode(); - } - return cachedHashCode; - } - - @Override - public String toString(){ - StringBuilder sb = new StringBuilder(); - int i = 0; - for (Map.Entry entry : defines.entrySet()) { - sb.append(entry.getKey()).append("=").append(entry.getValue()); - if (i != defines.size() - 1) { - sb.append(", "); - } - i++; - } - return sb.toString(); - } - -} +/* + * Copyright (c) 2009-2012 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.shader; + +import com.jme3.export.*; +import com.jme3.material.MatParam; +import com.jme3.material.TechniqueDef; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; + +public class DefineList implements Savable, Cloneable { + + private static final String ONE = "1"; + + private TreeMap defines = new TreeMap(); + private String compiled = null; + private int cachedHashCode = 0; + + public void write(JmeExporter ex) throws IOException{ + OutputCapsule oc = ex.getCapsule(this); + + String[] keys = new String[defines.size()]; + String[] vals = new String[defines.size()]; + + int i = 0; + for (Map.Entry define : defines.entrySet()){ + keys[i] = define.getKey(); + vals[i] = define.getValue(); + i++; + } + + oc.write(keys, "keys", null); + oc.write(vals, "vals", null); + } + + public void read(JmeImporter im) throws IOException{ + InputCapsule ic = im.getCapsule(this); + + String[] keys = ic.readStringArray("keys", null); + String[] vals = ic.readStringArray("vals", null); + for (int i = 0; i < keys.length; i++){ + defines.put(keys[i], vals[i]); + } + } + + public void clear() { + defines.clear(); + compiled = ""; + cachedHashCode = 0; + } + + public String get(String key){ + return defines.get(key); + } + + @Override + public DefineList clone() { + try { + DefineList clone = (DefineList) super.clone(); + clone.cachedHashCode = 0; + clone.compiled = null; + clone.defines = (TreeMap) defines.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public boolean set(String key, VarType type, Object val){ + if (val == null){ + defines.remove(key); + compiled = null; + cachedHashCode = 0; + return true; + } + + switch (type){ + case Boolean: + if (((Boolean) val).booleanValue()) { + // same literal, != will work + if (defines.put(key, ONE) != ONE) { + compiled = null; + cachedHashCode = 0; + return true; + } + } else if (defines.containsKey(key)) { + defines.remove(key); + compiled = null; + cachedHashCode = 0; + return true; + } + + break; + case Float: + case Int: + String newValue = val.toString(); + String original = defines.put(key, newValue); + if (!val.equals(original)) { + compiled = null; + cachedHashCode = 0; + return true; + } + break; + default: + // same literal, != will work + if (defines.put(key, ONE) != ONE) { + compiled = null; + cachedHashCode = 0; + return true; + } + break; + } + + return false; + } + + public boolean remove(String key){ + if (defines.remove(key) != null) { + compiled = null; + cachedHashCode = 0; + return true; + } + return false; + } + + public void addFrom(DefineList other){ + if (other == null) { + return; + } + compiled = null; + cachedHashCode = 0; + defines.putAll(other.defines); + } + + public String getCompiled(){ + if (compiled == null){ + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : defines.entrySet()){ + sb.append("#define ").append(entry.getKey()).append(" "); + sb.append(entry.getValue()).append('\n'); + } + compiled = sb.toString(); + } + return compiled; + } + + @Override + public boolean equals(Object obj) { + final DefineList other = (DefineList) obj; + return defines.equals(other.defines); + } + + public boolean equalsParams(Collection params, TechniqueDef def) { + + int size = 0; + + for (MatParam param : params) { + String key = def.getShaderParamDefine(param.getName()); + if (key != null) { + Object val = param.getValue(); + if (val != null) { + + switch (param.getVarType()) { + case Boolean: { + String current = defines.get(key); + if (((Boolean) val).booleanValue()) { + if (current == null || current != ONE) { + return false; + } + size++; + } else { + if (current != null) { + return false; + } + } + } + break; + case Float: + case Int: { + String newValue = val.toString(); + String current = defines.get(key); + if (!newValue.equals(current)) { + return false; + } + size++; + } + break; + default: { + if (!defines.containsKey(key)) { + return false; + } + size++; + } + break; + } + + } + + } + } + + if (size != defines.size()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + if (cachedHashCode == 0) { + cachedHashCode = defines.hashCode(); + } + return cachedHashCode; + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + int i = 0; + for (Map.Entry entry : defines.entrySet()) { + sb.append(entry.getKey()).append("=").append(entry.getValue()); + if (i != defines.size() - 1) { + sb.append(", "); + } + i++; + } + return sb.toString(); + } + +} diff --git a/engine/src/core/com/jme3/shadow/AbstractShadowRenderer.java b/engine/src/core/com/jme3/shadow/AbstractShadowRenderer.java index 47e6b1fb4..1666d2b8a 100644 --- a/engine/src/core/com/jme3/shadow/AbstractShadowRenderer.java +++ b/engine/src/core/com/jme3/shadow/AbstractShadowRenderer.java @@ -1,625 +1,632 @@ -/* - * Copyright (c) 2009-2012 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.shadow; - -import com.jme3.asset.AssetManager; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector3f; -import com.jme3.post.SceneProcessor; -import com.jme3.renderer.Camera; -import com.jme3.renderer.Caps; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.Renderer; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.GeometryList; -import com.jme3.renderer.queue.OpaqueComparator; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; -import com.jme3.scene.debug.WireFrustum; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image.Format; -import com.jme3.texture.Texture.MagFilter; -import com.jme3.texture.Texture.MinFilter; -import com.jme3.texture.Texture.ShadowCompareMode; -import com.jme3.texture.Texture2D; -import com.jme3.ui.Picture; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * abstract shadow renderer that holds commons feature to have for a shadow - * renderer - * - * @author Rémy Bouquet aka Nehon - */ -public abstract class AbstractShadowRenderer implements SceneProcessor, Savable { - - protected int nbShadowMaps = 1; - protected float shadowMapSize; - protected float shadowIntensity = 0.7f; - protected RenderManager renderManager; - protected ViewPort viewPort; - protected FrameBuffer[] shadowFB; - protected Texture2D[] shadowMaps; - protected Texture2D dummyTex; - protected Material preshadowMat; - protected Material postshadowMat; - protected Matrix4f[] lightViewProjectionsMatrices; - protected AssetManager assetManager; - protected boolean debug = false; - protected float edgesThickness = 1.0f; - protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear; - protected CompareMode shadowCompareMode = CompareMode.Hardware; - protected Picture[] dispPic; - protected boolean flushQueues = true; - // define if the fallback material should be used. - protected boolean needsfallBackMaterial = false; - //Name of the post material technique - protected String postTechniqueName = "PostShadow"; - //flags to know when to change params in the materials - //a list of material of the post shadow queue geometries. - protected List matCache = new ArrayList(); - protected GeometryList sceneReceivers; - protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); - protected GeometryList shadowMapOccluders = new GeometryList(new OpaqueComparator()); - - - /** - * used for serialization - */ - protected AbstractShadowRenderer(){ - } - - /** - * Create an abstract shadow renderer, this is to be called in extending - * classes - * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param nbShadowMaps the number of shadow maps rendered (the more shadow - * maps the more quality, the less fps). - */ - protected AbstractShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) { - - this.assetManager = assetManager; - this.nbShadowMaps = nbShadowMaps; - this.shadowMapSize = shadowMapSize; - init(assetManager, nbShadowMaps, shadowMapSize); - - } - - private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize) { - this.postshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PostShadow.j3md"); - shadowFB = new FrameBuffer[nbShadowMaps]; - shadowMaps = new Texture2D[nbShadowMaps]; - dispPic = new Picture[nbShadowMaps]; - lightViewProjectionsMatrices = new Matrix4f[nbShadowMaps]; - - //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) - dummyTex = new Texture2D(shadowMapSize, shadowMapSize, Format.RGBA8); - - preshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); - postshadowMat.setFloat("ShadowMapSize", shadowMapSize); - - for (int i = 0; i < nbShadowMaps; i++) { - lightViewProjectionsMatrices[i] = new Matrix4f(); - shadowFB[i] = new FrameBuffer(shadowMapSize, shadowMapSize, 1); - shadowMaps[i] = new Texture2D(shadowMapSize, shadowMapSize, Format.Depth); - - shadowFB[i].setDepthTexture(shadowMaps[i]); - - //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) - shadowFB[i].setColorTexture(dummyTex); - - postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]); - - //quads for debuging purpose - dispPic[i] = new Picture("Picture" + i); - dispPic[i].setTexture(assetManager, shadowMaps[i], false); - } - - setShadowCompareMode(shadowCompareMode); - setEdgeFilteringMode(edgeFilteringMode); - setShadowIntensity(shadowIntensity); - } - - /** - * set the post shadow material for this renderer - * - * @param postShadowMat - */ - protected final void setPostShadowMaterial(Material postShadowMat) { - this.postshadowMat = postShadowMat; - postshadowMat.setFloat("ShadowMapSize", shadowMapSize); - for (int i = 0; i < nbShadowMaps; i++) { - postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]); - } - setShadowCompareMode(shadowCompareMode); - setEdgeFilteringMode(edgeFilteringMode); - setShadowIntensity(shadowIntensity); - } - - /** - * Sets the filtering mode for shadow edges see {@link EdgeFilteringMode} - * for more info - * - * @param EdgeFilteringMode - */ - final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { - if (filterMode == null) { - throw new NullPointerException(); - } - - if (this.edgeFilteringMode == filterMode) { - return; - } - - this.edgeFilteringMode = filterMode; - postshadowMat.setInt("FilterMode", filterMode.getMaterialParamValue()); - postshadowMat.setFloat("PCFEdge", edgesThickness); - if (shadowCompareMode == CompareMode.Hardware) { - for (Texture2D shadowMap : shadowMaps) { - if (filterMode == EdgeFilteringMode.Bilinear) { - shadowMap.setMagFilter(MagFilter.Bilinear); - shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); - } else { - shadowMap.setMagFilter(MagFilter.Nearest); - shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); - } - } - } - } - - /** - * returns the the edge filtering mode - * - * @see EdgeFilteringMode - * @return - */ - public EdgeFilteringMode getEdgeFilteringMode() { - return edgeFilteringMode; - } - - /** - * sets the shadow compare mode see {@link CompareMode} for more info - * - * @param compareMode - */ - final public void setShadowCompareMode(CompareMode compareMode) { - if (compareMode == null) { - throw new IllegalArgumentException("Shadow compare mode cannot be null"); - } - - this.shadowCompareMode = compareMode; - for (Texture2D shadowMap : shadowMaps) { - if (compareMode == CompareMode.Hardware) { - shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual); - if (edgeFilteringMode == EdgeFilteringMode.Bilinear) { - shadowMap.setMagFilter(MagFilter.Bilinear); - shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); - } else { - shadowMap.setMagFilter(MagFilter.Nearest); - shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); - } - } else { - shadowMap.setShadowCompareMode(ShadowCompareMode.Off); - shadowMap.setMagFilter(MagFilter.Nearest); - shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); - } - } - postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware); - } - - /** - * returns the shadow compare mode - * - * @see CompareMode - * @return the shadowCompareMode - */ - public CompareMode getShadowCompareMode() { - return shadowCompareMode; - } - - //debug function that create a displayable frustrum - protected Geometry createFrustum(Vector3f[] pts, int i) { - WireFrustum frustum = new WireFrustum(pts); - Geometry frustumMdl = new Geometry("f", frustum); - frustumMdl.setCullHint(Spatial.CullHint.Never); - frustumMdl.setShadowMode(ShadowMode.Off); - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.getAdditionalRenderState().setWireframe(true); - frustumMdl.setMaterial(mat); - switch (i) { - case 0: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink); - break; - case 1: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); - break; - case 2: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green); - break; - case 3: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue); - break; - default: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.White); - break; - } - - frustumMdl.updateGeometricState(); - return frustumMdl; - } - - public void initialize(RenderManager rm, ViewPort vp) { - renderManager = rm; - viewPort = vp; - //checking for caps to chosse the appropriate post material technique - if (renderManager.getRenderer().getCaps().contains(Caps.GLSL150)) { - postTechniqueName = "PostShadow15"; - } else { - postTechniqueName = "PostShadow"; - } - } - - public boolean isInitialized() { - return viewPort != null; - } - - /** - * This mehtod is called once per frame. it is responsible for updating the - * shadow cams according to the light view. - * - * @param viewCam the scene cam - */ - protected abstract void updateShadowCams(Camera viewCam); - - /** - * this method must return the geomtryList that contains the oclluders to be - * rendered in the shadow map - * - * @param shadowMapIndex the index of the shadow map being rendered - * @param sceneOccluders the occluders of the whole scene - * @param sceneReceivers the recievers of the whole scene - * @return - */ - protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers, GeometryList shadowMapOccluders); - - /** - * return the shadow camera to use for rendering the shadow map according - * the given index - * - * @param shadowMapIndex the index of the shadow map being rendered - * @return the shadowCam - */ - protected abstract Camera getShadowCam(int shadowMapIndex); - - /** - * responsible for displaying the frustum of the shadow cam for debug - * purpose - * - * @param shadowMapIndex - */ - protected void doDisplayFrustumDebug(int shadowMapIndex) { - } - - @SuppressWarnings("fallthrough") - public void postQueue(RenderQueue rq) { - GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); - sceneReceivers = rq.getShadowQueueContent(ShadowMode.Receive); - if (sceneReceivers.size() == 0 || occluders.size() == 0) { - return; - } - - updateShadowCams(viewPort.getCamera()); - - Renderer r = renderManager.getRenderer(); - renderManager.setForcedMaterial(preshadowMat); - renderManager.setForcedTechnique("PreShadow"); - - for (int shadowMapIndex = 0; shadowMapIndex < nbShadowMaps; shadowMapIndex++) { - - if (debugfrustums) { - doDisplayFrustumDebug(shadowMapIndex); - } - renderShadowMap(shadowMapIndex, occluders, sceneReceivers); - - } - - debugfrustums = false; - if (flushQueues) { - occluders.clear(); - } - //restore setting for future rendering - r.setFrameBuffer(viewPort.getOutputFrameBuffer()); - renderManager.setForcedMaterial(null); - renderManager.setForcedTechnique(null); - renderManager.setCamera(viewPort.getCamera(), false); - - } - - protected void renderShadowMap(int shadowMapIndex, GeometryList occluders, GeometryList receivers) { - shadowMapOccluders = getOccludersToRender(shadowMapIndex, occluders, receivers, shadowMapOccluders); - Camera shadowCam = getShadowCam(shadowMapIndex); - - //saving light view projection matrix for this split - lightViewProjectionsMatrices[shadowMapIndex].set(shadowCam.getViewProjectionMatrix()); - renderManager.setCamera(shadowCam, false); - - renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]); - renderManager.getRenderer().clearBuffers(false, true, false); - - // render shadow casters to shadow map - viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); - } - boolean debugfrustums = false; - - public void displayFrustum() { - debugfrustums = true; - } - - //debug only : displays depth shadow maps - protected void displayShadowMap(Renderer r) { - Camera cam = viewPort.getCamera(); - renderManager.setCamera(cam, true); - int h = cam.getHeight(); - for (int i = 0; i < dispPic.length; i++) { - dispPic[i].setPosition((128 * i) + (150 + 64 * (i + 1)), h / 20f); - dispPic[i].setWidth(128); - dispPic[i].setHeight(128); - dispPic[i].updateGeometricState(); - renderManager.renderGeometry(dispPic[i]); - } - renderManager.setCamera(cam, false); - } - - /** - * For dubuging purpose Allow to "snapshot" the current frustrum to the - * scene - */ - public void displayDebug() { - debug = true; - } - - abstract GeometryList getReceivers(GeometryList sceneReceivers, GeometryList lightReceivers); - - public void postFrame(FrameBuffer out) { - - if (debug) { - displayShadowMap(renderManager.getRenderer()); - } - - lightReceivers = getReceivers(sceneReceivers, lightReceivers); - - if (lightReceivers.size() != 0) { - //setting params to recieving geometry list - setMatParams(); - - Camera cam = viewPort.getCamera(); - //some materials in the scene does not have a post shadow technique so we're using the fall back material - if (needsfallBackMaterial) { - renderManager.setForcedMaterial(postshadowMat); - } - - //forcing the post shadow technique and render state - renderManager.setForcedTechnique(postTechniqueName); - - //rendering the post shadow pass - viewPort.getQueue().renderShadowQueue(lightReceivers, renderManager, cam, true); - if (flushQueues) { - sceneReceivers.clear(); - } - - //resetting renderManager settings - renderManager.setForcedTechnique(null); - renderManager.setForcedMaterial(null); - renderManager.setCamera(cam, false); - - } - - } - - /** - * This method is called once per frame and is responsible of setting the - * material parameters than sub class may need to set on the post material - * - * @param material the materail to use for the post shadow pass - */ - protected abstract void setMaterialParameters(Material material); - - private void setMatParams() { - - GeometryList l = viewPort.getQueue().getShadowQueueContent(ShadowMode.Receive); - - //iteration throught all the geometries of the list to gather the materials - - matCache.clear(); - for (int i = 0; i < l.size(); i++) { - Material mat = l.get(i).getMaterial(); - //checking if the material has the post technique and adding it to the material cache - if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) { - if (!matCache.contains(mat)) { - matCache.add(mat); - } - } else { - needsfallBackMaterial = true; - } - } - - //iterating through the mat cache and setting the parameters - for (Material mat : matCache) { - - mat.setFloat("ShadowMapSize", shadowMapSize); - - for (int j = 0; j < nbShadowMaps; j++) { - mat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]); - } - for (int j = 0; j < nbShadowMaps; j++) { - mat.setTexture("ShadowMap" + j, shadowMaps[j]); - } - mat.setBoolean("HardwareShadows", shadowCompareMode == CompareMode.Hardware); - mat.setInt("FilterMode", edgeFilteringMode.getMaterialParamValue()); - mat.setFloat("PCFEdge", edgesThickness); - mat.setFloat("ShadowIntensity", shadowIntensity); - - setMaterialParameters(mat); - } - - //At least one material of the receiving geoms does not support the post shadow techniques - //so we fall back to the forced material solution (transparent shadows won't be supported for these objects) - if (needsfallBackMaterial) { - setPostShadowParams(); - } - - } - - /** - * for internal use only - */ - protected void setPostShadowParams() { - setMaterialParameters(postshadowMat); - for (int j = 0; j < nbShadowMaps; j++) { - postshadowMat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]); - postshadowMat.setTexture("ShadowMap" + j, shadowMaps[j]); - } - } - - public void preFrame(float tpf) { - } - - public void cleanup() { - } - - public void reshape(ViewPort vp, int w, int h) { - } - - /** - * returns the shdaow intensity - * - * @see #setShadowIntensity(float shadowIntensity) - * @return shadowIntensity - */ - public float getShadowIntensity() { - return shadowIntensity; - } - - /** - * Set the shadowIntensity, the value should be between 0 and 1, a 0 value - * gives a bright and invisilble shadow, a 1 value gives a pitch black - * shadow, default is 0.7 - * - * @param shadowIntensity the darkness of the shadow - */ - final public void setShadowIntensity(float shadowIntensity) { - this.shadowIntensity = shadowIntensity; - postshadowMat.setFloat("ShadowIntensity", shadowIntensity); - } - - /** - * returns the edges thickness - * - * @see #setEdgesThickness(int edgesThickness) - * @return edgesThickness - */ - public int getEdgesThickness() { - return (int) (edgesThickness * 10); - } - - /** - * Sets the shadow edges thickness. default is 1, setting it to lower values - * can help to reduce the jagged effect of the shadow edges - * - * @param edgesThickness - */ - public void setEdgesThickness(int edgesThickness) { - this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); - this.edgesThickness *= 0.1f; - postshadowMat.setFloat("PCFEdge", edgesThickness); - } - - /** - * returns true if the PssmRenderer flushed the shadow queues - * - * @return flushQueues - */ - public boolean isFlushQueues() { - return flushQueues; - } - - /** - * Set this to false if you want to use several PssmRederers to have - * multiple shadows cast by multiple light sources. Make sure the last - * PssmRenderer in the stack DO flush the queues, but not the others - * - * @param flushQueues - */ - public void setFlushQueues(boolean flushQueues) { - this.flushQueues = flushQueues; - } - - public void read(JmeImporter im) throws IOException { - InputCapsule ic = (InputCapsule) im.getCapsule(this); - assetManager = im.getAssetManager(); - nbShadowMaps = ic.readInt("nbShadowMaps", 1); - shadowMapSize = ic.readInt("shadowMapSize", 0); - shadowIntensity = ic.readFloat("shadowIntensity", 0.7f); - edgeFilteringMode = ic.readEnum("edgeFilteringMode", EdgeFilteringMode.class, EdgeFilteringMode.Bilinear); - shadowCompareMode = ic.readEnum("shadowCompareMode", CompareMode.class, CompareMode.Hardware); - flushQueues = ic.readBoolean("flushQueues", false); - init(assetManager, nbShadowMaps, (int) shadowMapSize); - edgesThickness = ic.readFloat("edgesThickness", 1.0f); - postshadowMat.setFloat("PCFEdge", edgesThickness); - - } - - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); - oc.write(nbShadowMaps, "nbShadowMaps", 1); - oc.write(shadowMapSize, "shadowMapSize", 0); - oc.write(shadowIntensity, "shadowIntensity", 0.7f); - oc.write(edgeFilteringMode, "edgeFilteringMode", EdgeFilteringMode.Bilinear); - oc.write(shadowCompareMode, "shadowCompareMode", CompareMode.Hardware); - oc.write(flushQueues, "flushQueues", false); - oc.write(edgesThickness, "edgesThickness", 1.0f); - } -} +/* + * Copyright (c) 2009-2012 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.shadow; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.OpaqueComparator; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture.ShadowCompareMode; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; + +/** + * abstract shadow renderer that holds commons feature to have for a shadow + * renderer + * + * @author Rémy Bouquet aka Nehon + */ +public abstract class AbstractShadowRenderer implements SceneProcessor, Savable { + + protected int nbShadowMaps = 1; + protected float shadowMapSize; + protected float shadowIntensity = 0.7f; + protected RenderManager renderManager; + protected ViewPort viewPort; + protected FrameBuffer[] shadowFB; + protected Texture2D[] shadowMaps; + protected Texture2D dummyTex; + protected Material preshadowMat; + protected Material postshadowMat; + protected Matrix4f[] lightViewProjectionsMatrices; + protected AssetManager assetManager; + protected boolean debug = false; + protected float edgesThickness = 1.0f; + protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear; + protected CompareMode shadowCompareMode = CompareMode.Hardware; + protected Picture[] dispPic; + protected boolean flushQueues = true; + // define if the fallback material should be used. + protected boolean needsfallBackMaterial = false; + //Name of the post material technique + protected String postTechniqueName = "PostShadow"; + //flags to know when to change params in the materials + //a list of material of the post shadow queue geometries. + protected List matCache = new ArrayList(); + protected GeometryList sceneReceivers; + protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); + protected GeometryList shadowMapOccluders = new GeometryList(new OpaqueComparator()); + private String[] shadowMapStringCache; + private String[] lightViewStringCache; + + + /** + * used for serialization + */ + protected AbstractShadowRenderer(){ + } + + /** + * Create an abstract shadow renderer, this is to be called in extending + * classes + * + * @param assetManager the application asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, + * etc...) + * @param nbShadowMaps the number of shadow maps rendered (the more shadow + * maps the more quality, the less fps). + */ + protected AbstractShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) { + + this.assetManager = assetManager; + this.nbShadowMaps = nbShadowMaps; + this.shadowMapSize = shadowMapSize; + init(assetManager, nbShadowMaps, shadowMapSize); + + } + + private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize) { + this.postshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PostShadow.j3md"); + shadowFB = new FrameBuffer[nbShadowMaps]; + shadowMaps = new Texture2D[nbShadowMaps]; + dispPic = new Picture[nbShadowMaps]; + lightViewProjectionsMatrices = new Matrix4f[nbShadowMaps]; + shadowMapStringCache = new String[nbShadowMaps]; + lightViewStringCache = new String[nbShadowMaps]; + + //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + dummyTex = new Texture2D(shadowMapSize, shadowMapSize, Format.RGBA8); + + preshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); + postshadowMat.setFloat("ShadowMapSize", shadowMapSize); + + for (int i = 0; i < nbShadowMaps; i++) { + lightViewProjectionsMatrices[i] = new Matrix4f(); + shadowFB[i] = new FrameBuffer(shadowMapSize, shadowMapSize, 1); + shadowMaps[i] = new Texture2D(shadowMapSize, shadowMapSize, Format.Depth); + + shadowFB[i].setDepthTexture(shadowMaps[i]); + + //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + shadowFB[i].setColorTexture(dummyTex); + shadowMapStringCache[i] = "ShadowMap" + i; + lightViewStringCache[i] = "LightViewProjectionMatrix" + i; + + postshadowMat.setTexture(shadowMapStringCache[i], shadowMaps[i]); + + //quads for debuging purpose + dispPic[i] = new Picture("Picture" + i); + dispPic[i].setTexture(assetManager, shadowMaps[i], false); + } + + setShadowCompareMode(shadowCompareMode); + setEdgeFilteringMode(edgeFilteringMode); + setShadowIntensity(shadowIntensity); + } + + /** + * set the post shadow material for this renderer + * + * @param postShadowMat + */ + protected final void setPostShadowMaterial(Material postShadowMat) { + this.postshadowMat = postShadowMat; + postshadowMat.setFloat("ShadowMapSize", shadowMapSize); + for (int i = 0; i < nbShadowMaps; i++) { + postshadowMat.setTexture(shadowMapStringCache[i], shadowMaps[i]); + } + setShadowCompareMode(shadowCompareMode); + setEdgeFilteringMode(edgeFilteringMode); + setShadowIntensity(shadowIntensity); + } + + /** + * Sets the filtering mode for shadow edges see {@link EdgeFilteringMode} + * for more info + * + * @param EdgeFilteringMode + */ + final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { + if (filterMode == null) { + throw new NullPointerException(); + } + + if (this.edgeFilteringMode == filterMode) { + return; + } + + this.edgeFilteringMode = filterMode; + postshadowMat.setInt("FilterMode", filterMode.getMaterialParamValue()); + postshadowMat.setFloat("PCFEdge", edgesThickness); + if (shadowCompareMode == CompareMode.Hardware) { + for (Texture2D shadowMap : shadowMaps) { + if (filterMode == EdgeFilteringMode.Bilinear) { + shadowMap.setMagFilter(MagFilter.Bilinear); + shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); + } else { + shadowMap.setMagFilter(MagFilter.Nearest); + shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); + } + } + } + } + + /** + * returns the the edge filtering mode + * + * @see EdgeFilteringMode + * @return + */ + public EdgeFilteringMode getEdgeFilteringMode() { + return edgeFilteringMode; + } + + /** + * sets the shadow compare mode see {@link CompareMode} for more info + * + * @param compareMode + */ + final public void setShadowCompareMode(CompareMode compareMode) { + if (compareMode == null) { + throw new IllegalArgumentException("Shadow compare mode cannot be null"); + } + + this.shadowCompareMode = compareMode; + for (Texture2D shadowMap : shadowMaps) { + if (compareMode == CompareMode.Hardware) { + shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual); + if (edgeFilteringMode == EdgeFilteringMode.Bilinear) { + shadowMap.setMagFilter(MagFilter.Bilinear); + shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); + } else { + shadowMap.setMagFilter(MagFilter.Nearest); + shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); + } + } else { + shadowMap.setShadowCompareMode(ShadowCompareMode.Off); + shadowMap.setMagFilter(MagFilter.Nearest); + shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); + } + } + postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware); + } + + /** + * returns the shadow compare mode + * + * @see CompareMode + * @return the shadowCompareMode + */ + public CompareMode getShadowCompareMode() { + return shadowCompareMode; + } + + //debug function that create a displayable frustrum + protected Geometry createFrustum(Vector3f[] pts, int i) { + WireFrustum frustum = new WireFrustum(pts); + Geometry frustumMdl = new Geometry("f", frustum); + frustumMdl.setCullHint(Spatial.CullHint.Never); + frustumMdl.setShadowMode(ShadowMode.Off); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + frustumMdl.setMaterial(mat); + switch (i) { + case 0: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink); + break; + case 1: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); + break; + case 2: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green); + break; + case 3: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue); + break; + default: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.White); + break; + } + + frustumMdl.updateGeometricState(); + return frustumMdl; + } + + public void initialize(RenderManager rm, ViewPort vp) { + renderManager = rm; + viewPort = vp; + //checking for caps to chosse the appropriate post material technique + if (renderManager.getRenderer().getCaps().contains(Caps.GLSL150)) { + postTechniqueName = "PostShadow15"; + } else { + postTechniqueName = "PostShadow"; + } + } + + public boolean isInitialized() { + return viewPort != null; + } + + /** + * This mehtod is called once per frame. it is responsible for updating the + * shadow cams according to the light view. + * + * @param viewCam the scene cam + */ + protected abstract void updateShadowCams(Camera viewCam); + + /** + * this method must return the geomtryList that contains the oclluders to be + * rendered in the shadow map + * + * @param shadowMapIndex the index of the shadow map being rendered + * @param sceneOccluders the occluders of the whole scene + * @param sceneReceivers the recievers of the whole scene + * @return + */ + protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers, GeometryList shadowMapOccluders); + + /** + * return the shadow camera to use for rendering the shadow map according + * the given index + * + * @param shadowMapIndex the index of the shadow map being rendered + * @return the shadowCam + */ + protected abstract Camera getShadowCam(int shadowMapIndex); + + /** + * responsible for displaying the frustum of the shadow cam for debug + * purpose + * + * @param shadowMapIndex + */ + protected void doDisplayFrustumDebug(int shadowMapIndex) { + } + + @SuppressWarnings("fallthrough") + public void postQueue(RenderQueue rq) { + GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); + sceneReceivers = rq.getShadowQueueContent(ShadowMode.Receive); + if (sceneReceivers.size() == 0 || occluders.size() == 0) { + return; + } + + updateShadowCams(viewPort.getCamera()); + + Renderer r = renderManager.getRenderer(); + renderManager.setForcedMaterial(preshadowMat); + renderManager.setForcedTechnique("PreShadow"); + + for (int shadowMapIndex = 0; shadowMapIndex < nbShadowMaps; shadowMapIndex++) { + + if (debugfrustums) { + doDisplayFrustumDebug(shadowMapIndex); + } + renderShadowMap(shadowMapIndex, occluders, sceneReceivers); + + } + + debugfrustums = false; + if (flushQueues) { + occluders.clear(); + } + //restore setting for future rendering + r.setFrameBuffer(viewPort.getOutputFrameBuffer()); + renderManager.setForcedMaterial(null); + renderManager.setForcedTechnique(null); + renderManager.setCamera(viewPort.getCamera(), false); + + } + + protected void renderShadowMap(int shadowMapIndex, GeometryList occluders, GeometryList receivers) { + shadowMapOccluders = getOccludersToRender(shadowMapIndex, occluders, receivers, shadowMapOccluders); + Camera shadowCam = getShadowCam(shadowMapIndex); + + //saving light view projection matrix for this split + lightViewProjectionsMatrices[shadowMapIndex].set(shadowCam.getViewProjectionMatrix()); + renderManager.setCamera(shadowCam, false); + + renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]); + renderManager.getRenderer().clearBuffers(false, true, false); + + // render shadow casters to shadow map + viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); + } + boolean debugfrustums = false; + + public void displayFrustum() { + debugfrustums = true; + } + + //debug only : displays depth shadow maps + protected void displayShadowMap(Renderer r) { + Camera cam = viewPort.getCamera(); + renderManager.setCamera(cam, true); + int h = cam.getHeight(); + for (int i = 0; i < dispPic.length; i++) { + dispPic[i].setPosition((128 * i) + (150 + 64 * (i + 1)), h / 20f); + dispPic[i].setWidth(128); + dispPic[i].setHeight(128); + dispPic[i].updateGeometricState(); + renderManager.renderGeometry(dispPic[i]); + } + renderManager.setCamera(cam, false); + } + + /** + * For dubuging purpose Allow to "snapshot" the current frustrum to the + * scene + */ + public void displayDebug() { + debug = true; + } + + abstract GeometryList getReceivers(GeometryList sceneReceivers, GeometryList lightReceivers); + + public void postFrame(FrameBuffer out) { + + if (debug) { + displayShadowMap(renderManager.getRenderer()); + } + + lightReceivers = getReceivers(sceneReceivers, lightReceivers); + + if (lightReceivers.size() != 0) { + //setting params to recieving geometry list + setMatParams(); + + Camera cam = viewPort.getCamera(); + //some materials in the scene does not have a post shadow technique so we're using the fall back material + if (needsfallBackMaterial) { + renderManager.setForcedMaterial(postshadowMat); + } + + //forcing the post shadow technique and render state + renderManager.setForcedTechnique(postTechniqueName); + + //rendering the post shadow pass + viewPort.getQueue().renderShadowQueue(lightReceivers, renderManager, cam, true); + if (flushQueues) { + sceneReceivers.clear(); + } + + //resetting renderManager settings + renderManager.setForcedTechnique(null); + renderManager.setForcedMaterial(null); + renderManager.setCamera(cam, false); + + } + + } + + /** + * This method is called once per frame and is responsible of setting the + * material parameters than sub class may need to set on the post material + * + * @param material the materail to use for the post shadow pass + */ + protected abstract void setMaterialParameters(Material material); + + private void setMatParams() { + + GeometryList l = viewPort.getQueue().getShadowQueueContent(ShadowMode.Receive); + + //iteration throught all the geometries of the list to gather the materials + + matCache.clear(); + for (int i = 0; i < l.size(); i++) { + Material mat = l.get(i).getMaterial(); + //checking if the material has the post technique and adding it to the material cache + if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) { + if (!matCache.contains(mat)) { + matCache.add(mat); + } + } else { + needsfallBackMaterial = true; + } + } + + //iterating through the mat cache and setting the parameters + for (Material mat : matCache) { + + mat.setFloat("ShadowMapSize", shadowMapSize); + + for (int j = 0; j < nbShadowMaps; j++) { + mat.setMatrix4(lightViewStringCache[j], lightViewProjectionsMatrices[j]); + } + for (int j = 0; j < nbShadowMaps; j++) { + mat.setTexture(shadowMapStringCache[j], shadowMaps[j]); + } + mat.setBoolean("HardwareShadows", shadowCompareMode == CompareMode.Hardware); + mat.setInt("FilterMode", edgeFilteringMode.getMaterialParamValue()); + mat.setFloat("PCFEdge", edgesThickness); + mat.setFloat("ShadowIntensity", shadowIntensity); + + setMaterialParameters(mat); + } + + //At least one material of the receiving geoms does not support the post shadow techniques + //so we fall back to the forced material solution (transparent shadows won't be supported for these objects) + if (needsfallBackMaterial) { + setPostShadowParams(); + } + + } + + /** + * for internal use only + */ + protected void setPostShadowParams() { + setMaterialParameters(postshadowMat); + for (int j = 0; j < nbShadowMaps; j++) { + postshadowMat.setMatrix4(lightViewStringCache[j], lightViewProjectionsMatrices[j]); + postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]); + } + } + + public void preFrame(float tpf) { + } + + public void cleanup() { + } + + public void reshape(ViewPort vp, int w, int h) { + } + + /** + * returns the shdaow intensity + * + * @see #setShadowIntensity(float shadowIntensity) + * @return shadowIntensity + */ + public float getShadowIntensity() { + return shadowIntensity; + } + + /** + * Set the shadowIntensity, the value should be between 0 and 1, a 0 value + * gives a bright and invisilble shadow, a 1 value gives a pitch black + * shadow, default is 0.7 + * + * @param shadowIntensity the darkness of the shadow + */ + final public void setShadowIntensity(float shadowIntensity) { + this.shadowIntensity = shadowIntensity; + postshadowMat.setFloat("ShadowIntensity", shadowIntensity); + } + + /** + * returns the edges thickness + * + * @see #setEdgesThickness(int edgesThickness) + * @return edgesThickness + */ + public int getEdgesThickness() { + return (int) (edgesThickness * 10); + } + + /** + * Sets the shadow edges thickness. default is 1, setting it to lower values + * can help to reduce the jagged effect of the shadow edges + * + * @param edgesThickness + */ + public void setEdgesThickness(int edgesThickness) { + this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); + this.edgesThickness *= 0.1f; + postshadowMat.setFloat("PCFEdge", edgesThickness); + } + + /** + * returns true if the PssmRenderer flushed the shadow queues + * + * @return flushQueues + */ + public boolean isFlushQueues() { + return flushQueues; + } + + /** + * Set this to false if you want to use several PssmRederers to have + * multiple shadows cast by multiple light sources. Make sure the last + * PssmRenderer in the stack DO flush the queues, but not the others + * + * @param flushQueues + */ + public void setFlushQueues(boolean flushQueues) { + this.flushQueues = flushQueues; + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + assetManager = im.getAssetManager(); + nbShadowMaps = ic.readInt("nbShadowMaps", 1); + shadowMapSize = ic.readInt("shadowMapSize", 0); + shadowIntensity = ic.readFloat("shadowIntensity", 0.7f); + edgeFilteringMode = ic.readEnum("edgeFilteringMode", EdgeFilteringMode.class, EdgeFilteringMode.Bilinear); + shadowCompareMode = ic.readEnum("shadowCompareMode", CompareMode.class, CompareMode.Hardware); + flushQueues = ic.readBoolean("flushQueues", false); + init(assetManager, nbShadowMaps, (int) shadowMapSize); + edgesThickness = ic.readFloat("edgesThickness", 1.0f); + postshadowMat.setFloat("PCFEdge", edgesThickness); + + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(nbShadowMaps, "nbShadowMaps", 1); + oc.write(shadowMapSize, "shadowMapSize", 0); + oc.write(shadowIntensity, "shadowIntensity", 0.7f); + oc.write(edgeFilteringMode, "edgeFilteringMode", EdgeFilteringMode.Bilinear); + oc.write(shadowCompareMode, "shadowCompareMode", CompareMode.Hardware); + oc.write(flushQueues, "flushQueues", false); + oc.write(edgesThickness, "edgesThickness", 1.0f); + } +}