diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
index 344a69b216..55c2db1e08 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
@@ -38,13 +38,13 @@
/**
* Wraps an array of Tween actions into an action object.
- *
*
* Notes :
- *
The sequence of tweens is determined by {@link com.jme3.anim.tween.Tweens} utility class and the {@link BaseAction} interpolates that sequence.
- *
This implementation mimics the {@link com.jme3.anim.tween.AbstractTween}, but it delegates the interpolation method {@link Tween#interpolate(double)}
+ *
+ *
The sequence of tweens is determined by {@link com.jme3.anim.tween.Tweens} utility class and the {@link BaseAction} interpolates that sequence.
+ *
This implementation mimics the {@link com.jme3.anim.tween.AbstractTween}, but it delegates the interpolation method {@link Tween#interpolate(double)}
* to the {@link BlendableAction} class.
- *
+ *
*
* Created by Nehon.
*
@@ -66,10 +66,11 @@ public abstract class Action implements JmeCloneable, Tween {
* Instantiates an action object that wraps a tween actions array by extracting their actions to the collection {@link Action#actions}.
*
* Notes :
- *
If intentions are to wrap some tween actions, then subclasses have to call this constructor, examples : {@link BlendableAction} and {@link BlendAction}.
- *
If intentions are to make an implementation of {@link Action} that shouldn't wrap tweens of actions, then subclasses shouldn't call this
+ *
+ *
If intentions are to wrap some tween actions, then subclasses have to call this constructor, examples : {@link BlendableAction} and {@link BlendAction}.
+ *
If intentions are to make an implementation of {@link Action} that shouldn't wrap tweens of actions, then subclasses shouldn't call this
* constructor, examples : {@link ClipAction} and {@link BaseAction}.
- *
+ *
*
* @param tweens the tween actions to be wrapped (not null).
*/
@@ -119,11 +120,12 @@ public double getSpeed() {
* Alters the speedup factor applied by the layer running this action.
*
* Notes:
- *
This factor controls the animation direction, if the speed is a positive value then the animation will run forward and vice versa.
- *
The speed factor gets applied, inside the {@link com.jme3.anim.AnimLayer}, on each interpolation step by this formula : time += tpf * action.getSpeed() * composer.globalSpeed.
- *
Default speed is 1.0, it plays the animation clips at their normal speed.
- *
Setting the speed factor to Zero will stop the animation, while setting it to a negative number will play the animation in a backward fashion.
- *
+ *
+ *
This factor controls the animation direction, if the speed is a positive value then the animation will run forward and vice versa.
+ *
The speed factor gets applied, inside the {@link com.jme3.anim.AnimLayer}, on each interpolation step by this formula : time += tpf * action.getSpeed() * composer.globalSpeed.
+ *
Default speed is 1.0, it plays the animation clips at their normal speed.
+ *
Setting the speed factor to Zero will stop the animation, while setting it to a negative number will play the animation in a backward fashion.
+ *
*
* @param speed the speed of frames.
*/
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
index edc819f7ff..928c168bc0 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
@@ -55,7 +55,6 @@
* //run the action within this layer
* animComposer.setCurrentAction("basicAction", ActionState.class.getSimpleName());
*
- *
* Created by Nehon.
*/
public class BaseAction extends Action {
diff --git a/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java b/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java
index 1c95730bbc..bbacb524a4 100644
--- a/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java
+++ b/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java
@@ -35,6 +35,7 @@
import com.jme3.profile.AppProfiler;
import com.jme3.profile.AppStep;
+import com.jme3.profile.FgStep;
import com.jme3.profile.SpStep;
import com.jme3.profile.VpStep;
import com.jme3.renderer.ViewPort;
@@ -204,4 +205,5 @@ public void vpStep(VpStep step, ViewPort vp, Bucket bucket) {
@Override
public void spStep(SpStep step, String... additionalInfo) {
}
+
}
diff --git a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java
index 2936a30072..dad7c538cd 100644
--- a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java
+++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java
@@ -57,6 +57,7 @@ public class DetailedProfiler implements AppProfiler {
private String curAppPath = null;
private String curVpPath = null;
private String curSpPath = null;
+ private String curFgPath = null;
private VpStep lastVpStep = null;
private final StringBuilder path = new StringBuilder(256);
@@ -185,6 +186,16 @@ public void spStep(SpStep step, String... additionalInfo) {
}
}
+
+ @Override
+ public void fgStep(FgStep step, String... additionalInfo) {
+ if (data != null) {
+ curFgPath = getPath("", additionalInfo);
+ path.setLength(0);
+ path.append(curAppPath).append("/").append(curVpPath).append(curFgPath);
+ addStep(path.toString(), System.nanoTime());
+ }
+ }
public Map getStats() {
if (data != null) {
diff --git a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java
index ef2a550ee9..33073b432d 100644
--- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java
+++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2024 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -336,6 +336,7 @@ private void initCamera() {
Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
guiViewPort = renderManager.createPostView("Gui Default", guiCam);
guiViewPort.setClearFlags(false, false, false);
+ //guiViewPort.setUseFrameGraphs(false);
}
/**
diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java
index 73762a1234..16cf9c8d01 100644
--- a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java
+++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java
@@ -39,6 +39,8 @@
import com.jme3.material.Material;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Caps;
+import com.jme3.renderer.framegraph.export.FrameGraphData;
+import com.jme3.renderer.framegraph.export.ModuleGraphData;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.OBJLoader;
import com.jme3.shader.ShaderGenerator;
@@ -364,6 +366,24 @@ public default List getClassLoaders() {
*/
public FilterPostProcessor loadFilter(String name);
+ /**
+ * Loads a framegraph *.j3g file with a FrameGraphKey.
+ *
+ * @param key asset key of the framegraph file to load
+ * @return loaded framegraph
+ * @see #loadAsset(com.jme3.asset.AssetKey)
+ */
+ public FrameGraphData loadFrameGraph(FrameGraphKey key);
+
+ /**
+ * Loads a framegraph *.j3g file with a FrameGraphKey.
+ *
+ * @param name asset name of the framegraph file to load
+ * @return loaded framegraph
+ * @see #loadAsset(com.jme3.asset.AssetKey)
+ */
+ public FrameGraphData loadFrameGraph(String name);
+
/**
* Sets the shaderGenerator to generate shaders based on shaderNodes.
* @param generator the shaderGenerator
diff --git a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
index a8ff73f2c4..218105369b 100644
--- a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
+++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
@@ -38,6 +38,8 @@
import com.jme3.material.Material;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Caps;
+import com.jme3.renderer.framegraph.export.FrameGraphData;
+import com.jme3.renderer.framegraph.export.ModuleGraphData;
import com.jme3.scene.Spatial;
import com.jme3.shader.Glsl100ShaderGenerator;
import com.jme3.shader.Glsl150ShaderGenerator;
@@ -455,6 +457,16 @@ public FilterPostProcessor loadFilter(FilterKey key) {
public FilterPostProcessor loadFilter(String name) {
return loadFilter(new FilterKey(name));
}
+
+ @Override
+ public FrameGraphData loadFrameGraph(FrameGraphKey key) {
+ return loadAsset(key);
+ }
+
+ @Override
+ public FrameGraphData loadFrameGraph(String name) {
+ return loadAsset(new FrameGraphKey(name));
+ }
/**
* {@inheritDoc}
diff --git a/jme3-core/src/main/java/com/jme3/asset/FrameGraphKey.java b/jme3-core/src/main/java/com/jme3/asset/FrameGraphKey.java
new file mode 100644
index 0000000000..92b3d80668
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/asset/FrameGraphKey.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2024 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.asset;
+
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.renderer.framegraph.export.FrameGraphData;
+
+/**
+ *
+ * @author codex
+ */
+public class FrameGraphKey extends AssetKey {
+
+ public FrameGraphKey(String name) {
+ super(name);
+ }
+ public FrameGraphKey() {
+ super();
+ }
+
+ @Override
+ public Class extends AssetCache> getCacheType() {
+ return null;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/asset/ModuleGraphKey.java b/jme3-core/src/main/java/com/jme3/asset/ModuleGraphKey.java
new file mode 100644
index 0000000000..04b27e95e3
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/asset/ModuleGraphKey.java
@@ -0,0 +1,28 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.asset;
+
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.renderer.framegraph.export.ModuleGraphData;
+
+/**
+ *
+ * @author codex
+ */
+public class ModuleGraphKey extends AssetKey {
+
+ public ModuleGraphKey(String name) {
+ super(name);
+ }
+ public ModuleGraphKey() {
+ super();
+ }
+
+ @Override
+ public Class extends AssetCache> getCacheType() {
+ return null;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java
index d07a039995..9f685d5987 100644
--- a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java
+++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java
@@ -65,7 +65,7 @@
* ec1.setPosition(new Vector3f(0,0,0));
* // ec2.setPosition(new Vector3f(0,0,10));
* 3. Tag the spatials that are part of the environment
- * scene.deepFirstTraversal(s->{
+ * scene.deepFirstTraversal(s -> {
* if(s.getUserData("isEnvNode")!=null){
* EnvironmentProbeControl.tagGlobal(s);
* // or ec1.tag(s);
diff --git a/jme3-core/src/main/java/com/jme3/export/InputCapsule.java b/jme3-core/src/main/java/com/jme3/export/InputCapsule.java
index 133ea34678..55b5ea0a66 100644
--- a/jme3-core/src/main/java/com/jme3/export/InputCapsule.java
+++ b/jme3-core/src/main/java/com/jme3/export/InputCapsule.java
@@ -39,6 +39,7 @@
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.BitSet;
+import java.util.Collection;
import java.util.Map;
/**
@@ -109,14 +110,32 @@ public interface InputCapsule {
// BinarySavable
-
+
public Savable readSavable(String name, Savable defVal) throws IOException;
public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException;
public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException;
-
+ public default T readSavable(String name, Class type, T defVal) throws IOException {
+ Savable s = readSavable(name, defVal);
+ if (s != defVal && type.isAssignableFrom(s.getClass())) {
+ return (T)s;
+ } else {
+ return defVal;
+ }
+ }
+ public default SavableObject readSavableObject(String name, SavableObject defVal) throws IOException {
+ return readSavable(name, SavableObject.class, defVal);
+ }
// ArrayLists
+ public default Collection readToCollection(String name, Collection target) throws IOException {
+ ArrayList list = readSavableArrayList(name, new ArrayList());
+ for (Object obj : list) {
+ target.add(obj);
+ }
+ return target;
+ }
+
public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException;
public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException;
public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException;
diff --git a/jme3-core/src/main/java/com/jme3/export/NullSavable.java b/jme3-core/src/main/java/com/jme3/export/NullSavable.java
index e3384a4ca7..4893d9e9ba 100644
--- a/jme3-core/src/main/java/com/jme3/export/NullSavable.java
+++ b/jme3-core/src/main/java/com/jme3/export/NullSavable.java
@@ -41,10 +41,12 @@
* @author Kirill Vainer
*/
public class NullSavable implements Savable {
+
+ public static final NullSavable INSTANCE = new NullSavable();
+
@Override
- public void write(JmeExporter ex) throws IOException {
- }
+ public void write(JmeExporter ex) throws IOException {}
@Override
- public void read(JmeImporter im) throws IOException {
- }
+ public void read(JmeImporter im) throws IOException {}
+
}
diff --git a/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java b/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java
index 1612a61c13..35ac81b8d3 100644
--- a/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java
+++ b/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java
@@ -39,13 +39,14 @@
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.BitSet;
+import java.util.Collection;
import java.util.Map;
/**
* @author Joshua Slack
*/
public interface OutputCapsule {
-
+
// byte primitive
public void write(byte value, String name, byte defVal) throws IOException;
@@ -115,7 +116,17 @@ public interface OutputCapsule {
// ArrayLists
-
+
+ public default void writeFromCollection(Collection collection, String name, boolean checkSavable) throws IOException {
+ ArrayList list = new ArrayList(collection.size());
+ for (Object obj : collection) {
+ if (!checkSavable || obj instanceof Savable) {
+ list.add(obj);
+ }
+ }
+ writeSavableArrayList(list, name, new ArrayList());
+ }
+
public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException;
public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException;
public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException;
diff --git a/jme3-core/src/main/java/com/jme3/export/Savable.java b/jme3-core/src/main/java/com/jme3/export/Savable.java
index 85957fc24f..004f381a12 100644
--- a/jme3-core/src/main/java/com/jme3/export/Savable.java
+++ b/jme3-core/src/main/java/com/jme3/export/Savable.java
@@ -40,6 +40,8 @@
* @author Kirill Vainer
*/
public interface Savable {
+
void write(JmeExporter ex) throws IOException;
void read(JmeImporter im) throws IOException;
+
}
diff --git a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java
index 2c63050a4d..42d5bc896c 100644
--- a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java
+++ b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java
@@ -252,4 +252,5 @@ private static Constructor findNoArgConstructor(String className)
return result;
}
+
}
diff --git a/jme3-core/src/main/java/com/jme3/export/SavableObject.java b/jme3-core/src/main/java/com/jme3/export/SavableObject.java
new file mode 100644
index 0000000000..4cdf4ac425
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/export/SavableObject.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2024 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.export;
+
+import com.jme3.util.IntMap;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Saves the internal object if it is a supported savable type.
+ *
+ * All types supported by Input/Output capsules are supported here,
+ * except all arrays.
+ *
+ * @author codex
+ */
+public class SavableObject implements Savable {
+
+ public static final SavableObject NULL = new SavableObject();
+ private static final String NAME = "name";
+ private static final String OBJECT = "object";
+ private static final String TYPE = "type";
+ private static final String NULL_TYPE = "Null";
+ private static final Logger LOG = Logger.getLogger(SavableObject.class.getName());
+
+ private String name;
+ private Object object;
+
+ public SavableObject() {}
+ public SavableObject(String name) {
+ this(name, null);
+ }
+ public SavableObject(Object object) {
+ this(null, object);
+ }
+ public SavableObject(String name, Object object) {
+ this.name = name;
+ this.object = object;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ if (object == null) {
+ return;
+ }
+ OutputCapsule out = ex.getCapsule(this);
+ out.write(name, NAME, null);
+ if (object instanceof Savable) {
+ out.write((Savable)object, OBJECT, null);
+ out.write("Savable", TYPE, NULL_TYPE);
+ } else if (object instanceof Savable[]) {
+ out.write((Savable[])object, OBJECT, new Savable[0]);
+ out.write("Savable[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Savable[][]) {
+ out.write((Savable[][])object, OBJECT, new Savable[0][0]);
+ out.write("Savable[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Integer) {
+ out.write((int)object, OBJECT, 0);
+ out.write("Integer", TYPE, NULL_TYPE);
+ } else if (object instanceof Integer[]) {
+ out.write((int[])object, OBJECT, new int[0]);
+ out.write("Integer[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Integer[][]) {
+ out.write((int[][])object, OBJECT, new int[0][0]);
+ out.write("Integer[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Float) {
+ out.write((float)object, OBJECT, 0);
+ out.write("Float", TYPE, NULL_TYPE);
+ } else if (object instanceof Float[]) {
+ out.write((float[])object, OBJECT, new float[0]);
+ out.write("Float[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Float[][]) {
+ out.write((float[][])object, OBJECT, new float[0][0]);
+ out.write("Float[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Double) {
+ out.write((double)object, OBJECT, 0);
+ out.write("Double", TYPE, NULL_TYPE);
+ } else if (object instanceof Double[]) {
+ out.write((double[])object, OBJECT, new double[0]);
+ out.write("Double[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Double[][]) {
+ out.write((double[][])object, OBJECT, new double[0][0]);
+ out.write("Double[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Boolean) {
+ out.write((boolean)object, OBJECT, false);
+ out.write("Boolean", TYPE, NULL_TYPE);
+ } else if (object instanceof Boolean[]) {
+ out.write((boolean[])object, OBJECT, new boolean[0]);
+ out.write("Boolean[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Boolean[][]) {
+ out.write((boolean[][])object, OBJECT, new boolean[0][0]);
+ out.write("Boolean[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Byte) {
+ out.write((byte)object, OBJECT, (byte)0);
+ out.write("Byte", TYPE, NULL_TYPE);
+ } else if (object instanceof Byte[]) {
+ out.write((byte[])object, OBJECT, new byte[0]);
+ out.write("Byte[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Byte[][]) {
+ out.write((byte[][])object, OBJECT, new byte[0][0]);
+ out.write("Byte[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof String) {
+ out.write((String)object, OBJECT, null);
+ out.write("String", TYPE, NULL_TYPE);
+ } else if (object instanceof String[]) {
+ out.write((String[])object, OBJECT, new String[0]);
+ out.write("String[]", TYPE, NULL_TYPE);
+ } else if (object instanceof String[][]) {
+ out.write((String[][])object, OBJECT, new String[0][0]);
+ out.write("String[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Long) {
+ out.write((Long)object, OBJECT, 0);
+ out.write("Long", TYPE, NULL_TYPE);
+ } else if (object instanceof Long[]) {
+ out.write((long[])object, OBJECT, new long[0]);
+ out.write("Long[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Long[][]) {
+ out.write((long[][])object, OBJECT, new long[0][0]);
+ out.write("Long[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Short) {
+ out.write((short)object, OBJECT, (short)0);
+ out.write("Short", TYPE, NULL_TYPE);
+ } else if (object instanceof Short[]) {
+ out.write((short[])object, OBJECT, new short[0]);
+ out.write("Short[]", TYPE, NULL_TYPE);
+ } else if (object instanceof Short[][]) {
+ out.write((short[][])object, OBJECT, new short[0][0]);
+ out.write("Short[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof BitSet) {
+ out.write((BitSet)object, OBJECT, null);
+ out.write("BitSet", TYPE, NULL_TYPE);
+ } else if (object instanceof FloatBuffer) {
+ out.write((FloatBuffer)object, OBJECT, null);
+ out.write("FloatBuffer", TYPE, NULL_TYPE);
+ } else if (object instanceof IntBuffer) {
+ out.write((IntBuffer)object, OBJECT, null);
+ out.write("IntBuffer", TYPE, NULL_TYPE);
+ } else if (object instanceof ByteBuffer) {
+ out.write((ByteBuffer)object, OBJECT, null);
+ out.write("ByteBuffer", TYPE, NULL_TYPE);
+ } else if (object instanceof ShortBuffer) {
+ out.write((ShortBuffer)object, OBJECT, null);
+ out.write("ShortBuffer", TYPE, NULL_TYPE);
+ } else if (object instanceof ArrayList) {
+ out.writeSavableArrayList((ArrayList)object, OBJECT, null);
+ out.write("ArrayList", TYPE, NULL_TYPE);
+ } else if (object instanceof ArrayList[]) {
+ out.writeSavableArrayListArray((ArrayList[])object, OBJECT, new ArrayList[0]);
+ out.write("ArrayList[]", TYPE, NULL_TYPE);
+ } else if (object instanceof ArrayList[][]) {
+ out.writeSavableArrayListArray2D((ArrayList[][])object, OBJECT, new ArrayList[0][0]);
+ out.write("ArrayList[][]", TYPE, NULL_TYPE);
+ } else if (object instanceof Map) {
+ out.writeStringSavableMap((Map)object, OBJECT, null);
+ out.write("StringSavableMap", TYPE, NULL_TYPE);
+ } else if (object instanceof IntMap) {
+ out.writeIntSavableMap((IntMap)object, OBJECT, null);
+ out.write("IntMap", TYPE, NULL_TYPE);
+ } else {
+ String type = object.getClass().getName();
+ LOG.log(Level.WARNING, "Attempted to save unsupported type {0}", type);
+ out.write(type, TYPE, NULL_TYPE);
+ }
+ }
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ name = in.readString(NAME, null);
+ String type = in.readString(TYPE, NULL_TYPE);
+ switch (type) {
+ case "Savable":
+ object = in.readSavable(OBJECT, null);
+ break;
+ case "Savable[]":
+ object = in.readSavableArray(OBJECT, new Savable[0]);
+ break;
+ case "Savable[][]":
+ object = in.readSavableArray2D(OBJECT, new Savable[0][0]);
+ break;
+ case "Integer":
+ object = in.readInt(OBJECT, 0);
+ break;
+ case "Integer[]":
+ object = in.readIntArray(OBJECT, new int[0]);
+ break;
+ case "Integer[][]":
+ object = in.readIntArray2D(OBJECT, new int[0][0]);
+ break;
+ case "Float":
+ object = in.readFloat(OBJECT, 0);
+ break;
+ case "Float[]":
+ object = in.readFloatArray(OBJECT, new float[0]);
+ break;
+ case "Float[][]":
+ object = in.readFloatArray2D(OBJECT, new float[0][0]);
+ break;
+ case "Double":
+ object = in.readDouble(OBJECT, 0);
+ break;
+ case "Double[]":
+ object = in.readDoubleArray(OBJECT, new double[0]);
+ break;
+ case "Double[][]":
+ object = in.readDoubleArray2D(OBJECT, new double[0][0]);
+ break;
+ case "Boolean":
+ object = in.readBoolean(OBJECT, false);
+ break;
+ case "Boolean[]":
+ object = in.readBooleanArray(OBJECT, new boolean[0]);
+ break;
+ case "Boolean[][]":
+ object = in.readBooleanArray2D(OBJECT, new boolean[0][0]);
+ break;
+ case "Byte":
+ object = in.readByte(OBJECT, (byte)0);
+ break;
+ case "Byte[]":
+ object = in.readByteArray(OBJECT, new byte[0]);
+ break;
+ case "Byte[][]":
+ object = in.readByteArray2D(OBJECT, new byte[0][0]);
+ break;
+ case "String":
+ object = in.readString(OBJECT, null);
+ break;
+ case "String[]":
+ object = in.readStringArray(OBJECT, new String[0]);
+ break;
+ case "String[][]":
+ object = in.readStringArray2D(OBJECT, new String[0][0]);
+ break;
+ case "Long":
+ object = in.readLong(OBJECT, 0);
+ break;
+ case "Long[]":
+ object = in.readLongArray(OBJECT, new long[0]);
+ break;
+ case "Long[][]":
+ object = in.readLongArray2D(OBJECT, new long[0][0]);
+ break;
+ case "Short":
+ object = in.readShort(OBJECT, (short)0);
+ break;
+ case "Short[]":
+ object = in.readShortArray(OBJECT, new short[0]);
+ break;
+ case "Short[][]":
+ object = in.readShortArray2D(OBJECT, new short[0][0]);
+ break;
+ case "BitSet":
+ object = in.readBitSet(OBJECT, null);
+ break;
+ case "FloatBuffer":
+ object = in.readFloatBuffer(OBJECT, null);
+ break;
+ case "IntBuffer":
+ object = in.readIntBuffer(OBJECT, null);
+ break;
+ case "ByteBuffer":
+ object = in.readByteBuffer(OBJECT, null);
+ break;
+ case "ShortBuffer":
+ object = in.readShortBuffer(OBJECT, null);
+ break;
+ case "ArrayList":
+ object = in.readSavableArrayList(OBJECT, null);
+ break;
+ case "ArrayList[]":
+ object = in.readSavableArrayListArray(OBJECT, new ArrayList[0]);
+ break;
+ case "ArrayList[][]":
+ object = in.readSavableArrayListArray2D(OBJECT, new ArrayList[0][0]);
+ break;
+ case "StringSavableMap":
+ object = in.readStringSavableMap(OBJECT, null);
+ break;
+ case "IntMap":
+ object = in.readIntSavableMap(OBJECT, null);
+ break;
+ case NULL_TYPE:
+ object = null;
+ break;
+ default:
+ LOG.log(Level.WARNING, "Attempted to load unsupported type {0}", type);
+ break;
+ }
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ public void setObject(Object object) {
+ this.object = object;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public Object getObject() {
+ return object;
+ }
+ public T getObject(Class type) {
+ return (T)object;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/material/MaterialDef.java b/jme3-core/src/main/java/com/jme3/material/MaterialDef.java
index 9cbd482e00..8c3755548c 100644
--- a/jme3-core/src/main/java/com/jme3/material/MaterialDef.java
+++ b/jme3-core/src/main/java/com/jme3/material/MaterialDef.java
@@ -185,6 +185,24 @@ public void addTechniqueDef(TechniqueDef technique) {
public List getTechniqueDefs(String name) {
return techniques.get(name);
}
+
+ /**
+ * Gets the first TechniqueDef under the name of the given light mode.
+ *
+ * @param name
+ * @param mode
+ * @return first matching TechniqueDef, or null
+ */
+ public TechniqueDef getTechniqueDef(String name, TechniqueDef.LightMode mode) {
+ List list = techniques.get(name);
+ if (list == null) return null;
+ for (TechniqueDef d : list) {
+ if (d.getLightMode() == mode) {
+ return d;
+ }
+ }
+ return null;
+ }
/**
*
diff --git a/jme3-core/src/main/java/com/jme3/material/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java
index 5091028a85..437ef785b5 100644
--- a/jme3-core/src/main/java/com/jme3/material/Technique.java
+++ b/jme3-core/src/main/java/com/jme3/material/Technique.java
@@ -164,8 +164,7 @@ Shader makeCurrent(RenderManager renderManager, SafeArrayList
* @param lastTexUnit the index of the most recently used texture unit
*/
void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
- TechniqueDefLogic logic = def.getLogic();
- logic.render(renderManager, shader, geometry, lights, lastBindUnits);
+ def.getLogic().render(renderManager, shader, geometry, lights, lastBindUnits);
}
/**
diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
index b620c9ceaf..40d1df9612 100644
--- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
+++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
@@ -41,6 +41,7 @@
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
+import java.util.function.Function;
/**
* Describes a technique definition.
@@ -66,6 +67,7 @@ public class TechniqueDef implements Savable, Cloneable {
* Describes light rendering mode.
*/
public enum LightMode {
+
/**
* Disable light-based rendering
*/
@@ -105,6 +107,7 @@ public enum LightMode {
*/
@Deprecated
FixedPipeline,
+
/**
* Similar to {@link #SinglePass} except the type of each light is known
* at shader compile time.
@@ -115,7 +118,13 @@ public enum LightMode {
* shaders used balloons because of the variations in the number of
* lights used by objects.
*/
- StaticPass
+ StaticPass,
+
+ /**
+ *
+ */
+ Custom,
+
}
public enum ShadowMode {
@@ -157,6 +166,7 @@ public enum LightSpace {
private LightMode lightMode = LightMode.Disable;
private ShadowMode shadowMode = ShadowMode.Disable;
private TechniqueDefLogic logic;
+ private boolean logicInit = false;
private ArrayList worldBinds;
//The space in which the light should be transposed before sending to the shader.
@@ -242,10 +252,36 @@ public void setLightMode(LightMode lightMode) {
public void setLogic(TechniqueDefLogic logic) {
this.logic = logic;
}
-
+
public TechniqueDefLogic getLogic() {
return logic;
}
+
+ public T getLogic(Class type) {
+ if (logic == null) return null;
+ if (!type.isAssignableFrom(logic.getClass())) {
+ throw new ClassCastException("TechniqueDefLogic is not of "+type.getName());
+ }
+ return (T)logic;
+ }
+
+ /**
+ * Sets the technique def logic if the current logic is null or of a different type.
+ *
+ * @param
+ * @param type
+ * @param func
+ * @return
+ */
+ public T setLogicOrElse(Class type, Function func) {
+ if (logic == null || !type.isAssignableFrom(logic.getClass())) {
+ T l = func.apply(this);
+ setLogic(l);
+ return l;
+ } else {
+ return (T)logic;
+ }
+ }
/**
* Returns the shadow mode.
@@ -439,7 +475,6 @@ public void addShaderParamDefine(String paramName, VarType paramType, String def
*/
public int addShaderUnmappedDefine(String defineName, VarType defineType) {
int defineId = defineNames.size();
-
defineNames.add(defineName);
defineTypes.add(defineType);
return defineId;
@@ -514,13 +549,13 @@ private Shader loadShader(AssetManager assetManager, EnumSet rendererCaps,
}
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;
- }
+ 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.
@@ -529,7 +564,7 @@ public Shader getShader(AssetManager assetManager, EnumSet rendererCaps, D
* @param shaderLanguages EnumMap containing all shader languages for this stage
*/
public void setShaderFile(EnumMap shaderNames,
- EnumMap shaderLanguages) {
+ EnumMap shaderLanguages) {
requiredCaps.clear();
weight = 0;
@@ -815,7 +850,7 @@ public TechniqueDef clone() throws CloneNotSupportedException {
try {
clone.logic = logic.getClass().getConstructor(TechniqueDef.class).newInstance(clone);
} catch (InstantiationException | IllegalAccessException
- | NoSuchMethodException | InvocationTargetException e) {
+ | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
diff --git a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
index ca8f7d1efa..55a76b7c99 100644
--- a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
+++ b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
@@ -59,7 +59,15 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager
EnumSet rendererCaps, LightList lights, DefineList defines) {
return techniqueDef.getShader(assetManager, rendererCaps, defines);
}
-
+
+ /**
+ * Renders mesh from geometry.
+ *
+ * @param renderer
+ * @param geom
+ * @deprecated use {@link com.jme3.renderer.TechniqueDefLogic#renderMeshFromGeometry(com.jme3.renderer.Renderer, com.jme3.scene.Geometry)} instead.
+ */
+ @Deprecated
public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
Mesh mesh = geom.getMesh();
int lodLevel = geom.getLodLevel();
@@ -95,6 +103,6 @@ protected static ColorRGBA getAmbientColor(LightList lightList, boolean removeLi
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
Renderer renderer = renderManager.getRenderer();
renderer.setShader(shader);
- renderMeshFromGeometry(renderer, geometry);
+ TechniqueDefLogic.renderMeshFromGeometry(renderer, geometry);
}
}
diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SkyLightAndReflectionProbeRender.java b/jme3-core/src/main/java/com/jme3/material/logic/SkyLightAndReflectionProbeRender.java
new file mode 100644
index 0000000000..4972452df3
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/material/logic/SkyLightAndReflectionProbeRender.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2023 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.logic;
+
+import com.jme3.light.AmbientLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.light.LightProbe;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.TextureUnitException;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.texture.TextureCubeMap;
+
+import java.util.List;
+
+/**
+ * Rendering logic for handling SkyLight and ReflectionProbe.
+ *
+ * todo:The functionality of this tool class is not yet complete, because LightProbe has not yet been split into SkyLight and ReflectionProbe. So the code here is just to be compatible with the extraction of LightProbe and lay the groundwork for subsequent work.
+ * @author JohnKkk
+ */
+public class SkyLightAndReflectionProbeRender {
+
+ /**
+ * Update lightProbe data to current shader (currently data comes from lightProbe, later this method will be used for skyLight and reflectionProbe)
+ * @param rm
+ * @param lastTexUnit
+ * @param lightProbeData
+ * @param shCoeffs
+ * @param lightProbePemMap
+ * @param lightProbe
+ * @return
+ */
+ public static int setSkyLightAndReflectionProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, Uniform lightProbePemMap, LightProbe lightProbe) {
+
+ lightProbeData.setValue(VarType.Matrix4, lightProbe.getUniformMatrix());
+ //setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / area.getRadius() + lightProbe.getNbMipMaps(), 0);
+ shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
+ /*
+ * Assign the prefiltered env map to the next available texture unit.
+ */
+ int pemUnit = lastTexUnit++;
+ Renderer renderer = rm.getRenderer();
+ TextureCubeMap pemTexture = lightProbe.getPrefilteredEnvMap();
+ try {
+ renderer.setTexture(pemUnit, pemTexture);
+ } catch (TextureUnitException exception) {
+ String message = "Can't assign texture unit for SkyLightAndReflectionProbe."
+ + " lastTexUnit=" + lastTexUnit;
+ throw new IllegalArgumentException(message);
+ }
+ lightProbePemMap.setValue(VarType.Int, pemUnit);
+ return lastTexUnit;
+ }
+
+ /**
+ * Extract which lightProbes should affect the currently rendered object. Currently, this method is only used in deferredPath and only works for the first three collected lightProbes, so it is problematic, but I put it here to prepare for future functionality (and compatibilty with current lightProbes).
+ * @param lightList
+ * @param ambientLightColor
+ * @param skyLightAndReflectionProbes
+ * @param removeLights
+ * @return hasAmbientLight
+ */
+ public static boolean extractSkyLightAndReflectionProbes(LightList lightList, ColorRGBA ambientLightColor, List skyLightAndReflectionProbes, boolean removeLights) {
+ ambientLightColor.set(0, 0, 0, 1);
+ boolean hasAmbientLight = false;
+ skyLightAndReflectionProbes.clear();
+ for (int j = 0; j < lightList.size(); j++) {
+ Light l = lightList.get(j);
+ if (l instanceof AmbientLight) {
+ ambientLightColor.addLocal(l.getColor());
+ hasAmbientLight = true;
+ if(removeLights){
+ lightList.remove(j);
+ j--;
+ }
+ }
+ if (l instanceof LightProbe) {
+ skyLightAndReflectionProbes.add((LightProbe) l);
+ if(removeLights){
+ lightList.remove(j);
+ j--;
+ }
+ }
+ }
+ // todo:For reflection probes, only top three in view frustum are processed per frame (but scene can contain large amount of reflection probes)
+ if(skyLightAndReflectionProbes.size() > 3){
+
+ }
+ ambientLightColor.a = 1.0f;
+ return hasAmbientLight;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
index 31f970a176..475d13ffae 100644
--- a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
+++ b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
@@ -34,9 +34,13 @@
import com.jme3.asset.AssetManager;
import com.jme3.light.LightList;
import com.jme3.material.Material.BindUnits;
+import com.jme3.material.TechniqueDef;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.instancing.InstancedGeometry;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
@@ -91,7 +95,27 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager
* {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.light.LightList, com.jme3.shader.DefineList)}.
* @param geometry The geometry to render
* @param lights Lights which influence the geometry.
- * @param lastTexUnit the index of the most recently used texture unit
+ * @param lastBindUnits the index of the most recently used units
*/
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits);
+
+ /**
+ *
+ * @param renderer
+ * @param geom
+ */
+ public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
+ Mesh mesh = geom.getMesh();
+ int lodLevel = geom.getLodLevel();
+ if (geom instanceof InstancedGeometry) {
+ InstancedGeometry instGeom = (InstancedGeometry) geom;
+ int numVisibleInstances = instGeom.getNumVisibleInstances();
+ if (numVisibleInstances > 0) {
+ renderer.renderMesh(mesh, lodLevel, numVisibleInstances, instGeom.getAllInstanceData());
+ }
+ } else {
+ renderer.renderMesh(mesh, lodLevel, 1, null);
+ }
+ }
+
}
diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java
index 2993c7eed0..c0eacfdd5d 100644
--- a/jme3-core/src/main/java/com/jme3/math/FastMath.java
+++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java
@@ -843,6 +843,18 @@ public static float determinant(double m00, double m01, double m02,
* (m10 * det12 - m11 * det02 + m12 * det01));
}
+ /**
+ * Returns a random float between min and max.
+ *
+ * @param min the desired minimum value
+ * @param max the desired maximum value
+ * @return A random float between min (inclusive) to max (inclusive).
+ */
+ public static float nextRandomFloat(float min, float max) {
+ float f = (nextRandomFloat() * (max - min + 1.0f)) + min;
+ return Math.min(Math.max(f, min), max);
+ }
+
/**
* Returns a random float between 0 and 1.
*
diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java
index efdb19b04a..dc71f0deae 100644
--- a/jme3-core/src/main/java/com/jme3/post/Filter.java
+++ b/jme3-core/src/main/java/com/jme3/post/Filter.java
@@ -248,7 +248,7 @@ protected Filter() {
* @param w the width
* @param h the height
*/
- protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ public final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
// cleanup(renderManager.getRenderer());
defaultPass = new Pass();
defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat());
@@ -260,7 +260,7 @@ protected final void init(AssetManager manager, RenderManager renderManager, Vie
*
* @param r the Renderer
*/
- protected final void cleanup(Renderer r) {
+ public final void cleanup(Renderer r) {
processor = null;
if (defaultPass != null) {
defaultPass.cleanup(r);
@@ -301,6 +301,15 @@ protected void cleanUpFilter(Renderer r) {
*/
protected abstract Material getMaterial();
+ /**
+ * Public mirror of {@link #getMaterial()}.
+ *
+ * @return
+ */
+ public Material getPassMaterial() {
+ return getMaterial();
+ }
+
/**
* Override if you want to do something special with the depth texture;
*
@@ -326,7 +335,16 @@ protected void postQueue(RenderQueue queue) {
*/
protected void preFrame(float tpf) {
}
-
+
+ /**
+ * Public mirror of {@link #preFrame(float)}.
+ *
+ * @param tpf
+ */
+ public void filterPreFrame(float tpf) {
+
+ }
+
/**
* Override this method if you want to make a pass just after the frame has been rendered and just before the filter rendering
*
@@ -445,11 +463,38 @@ protected boolean isRequiresBilinear() {
return false;
}
+ /**
+ * Returns true if this filter can be used by framegraph passes.
+ *
+ * @return
+ */
+ public boolean isFrameGraphCompatible() {
+ return false;
+ }
+
+ /**
+ * Public mirror of {@link #isRequiresSceneTexture()}
+ *
+ * @return
+ */
+ public boolean isReqSceneTex() {
+ return isRequiresSceneTexture();
+ }
+
+ /**
+ * Public mirror of {@link #isRequiresDepthTexture()}
+ *
+ * @return
+ */
+ public boolean isReqDepthTex() {
+ return isRequiresDepthTexture();
+ }
+
/**
* returns the list of the postRender passes
* @return the pre-existing List
*/
- protected List getPostRenderPasses() {
+ public List getPostRenderPasses() {
return postRenderPasses;
}
@@ -492,4 +537,15 @@ protected void setProcessor(FilterPostProcessor proc) {
*/
protected void postFilter(Renderer r, FrameBuffer buffer){
}
+
+ /**
+ * Public mirror of {@link #postFilter(com.jme3.renderer.Renderer, com.jme3.texture.FrameBuffer)}
+ *
+ * @param r
+ * @param buffer
+ */
+ public void filterPostRender(Renderer r, FrameBuffer buffer) {
+ postFilter(r, buffer);
+ }
+
}
diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
index 7da3453b8f..c775240c54 100644
--- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
+++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
@@ -658,4 +658,5 @@ private void setupViewPortFrameBuffer() {
viewPort.setOutputFrameBuffer(renderFrameBuffer);
}
}
- }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java
index e3efc8e3af..e761c530d7 100644
--- a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java
+++ b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java
@@ -73,12 +73,28 @@ public interface AppProfiler {
/**
* Called at the beginning of the specified SpStep (SceneProcessor step).
- * For more detailed steps it is possible to provide additional information as strings, like the name of the processor.
+ *
+ * For more detailed steps it is possible to provide additional information as strings,
+ * like the name of the processor.
*
* @param step the SceneProcessor step that's about to begin
* @param additionalInfo information about the SceneProcessor step
*/
public void spStep(SpStep step, String... additionalInfo);
+
+ /**
+ * Called at the beginning of the specified FgStep (FrameGraph step).
+ *
+ * For more detailed steps it is possible to provide additional information as strings,
+ * such as the name of the pass.
+ *
+ * @param step
+ * @param additionalInfo
+ */
+ public default void fgStep(FgStep step, String... additionalInfo) {
+ // implementation is optional
+ }
+
}
diff --git a/jme3-core/src/main/java/com/jme3/profile/FgStep.java b/jme3-core/src/main/java/com/jme3/profile/FgStep.java
new file mode 100644
index 0000000000..245a518a85
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/profile/FgStep.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 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.profile;
+
+/**
+ * Defines steps in executing framegraphs.
+ *
+ * @author codex
+ */
+public enum FgStep {
+ Prepare,
+ Execute,
+ Reset,
+}
diff --git a/jme3-core/src/main/java/com/jme3/profile/VpStep.java b/jme3-core/src/main/java/com/jme3/profile/VpStep.java
index 9dff853681..fa78f82efe 100644
--- a/jme3-core/src/main/java/com/jme3/profile/VpStep.java
+++ b/jme3-core/src/main/java/com/jme3/profile/VpStep.java
@@ -48,6 +48,10 @@ public enum VpStep {
PostFrame,
ProcEndRender,
RenderBucket,
+ FrameGraphSetup,
+ FrameGraphCull,
+ FrameGraphExecute,
+ FrameGraphReset,
EndRender
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/AbstractPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/AbstractPipelineContext.java
new file mode 100644
index 0000000000..47773fa363
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/AbstractPipelineContext.java
@@ -0,0 +1,45 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.renderer;
+
+import java.util.LinkedList;
+
+/**
+ *
+ * @author codex
+ */
+public abstract class AbstractPipelineContext implements PipelineContext {
+
+ private final LinkedList usedPipelines = new LinkedList<>();
+
+ @Override
+ public boolean addPipeline(RenderManager rm, RenderPipeline pipeline) {
+ boolean firstCall = false;
+ if (!pipeline.hasRenderedThisFrame()) {
+ if (firstCall = usedPipelines.isEmpty()) {
+ beginRenderFrame(rm);
+ }
+ pipeline.beginRenderFrame(rm);
+ usedPipelines.add(pipeline);
+ } else if (usedPipelines.isEmpty()) {
+ throw new IllegalStateException(
+ "Pipeline cannot have rendered at this point, but claims it did.");
+ }
+ return firstCall;
+ }
+ @Override
+ public void flushPipelineStack(RenderManager rm) {
+ for (RenderPipeline p : usedPipelines) {
+ p.endRenderFrame(rm);
+ }
+ usedPipelines.clear();
+ endRenderFrame(rm);
+ }
+
+ protected abstract void beginRenderFrame(RenderManager rm);
+
+ protected abstract void endRenderFrame(RenderManager rm);
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/Camera.java b/jme3-core/src/main/java/com/jme3/renderer/Camera.java
index eb7cca3ae4..af48217267 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/Camera.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/Camera.java
@@ -518,6 +518,25 @@ public void resize(int width, int height, boolean fixAspect) {
onFrustumChange();
}
}
+
+ /**
+ * Resizes the camera's view to the width and height only if the
+ * that would change the camera's view size.
+ *
+ * @param width
+ * @param height
+ * @param fixAspect
+ * @param force
+ * @return true if camera was resized (or forced)
+ * @see #resize(int, int, boolean)
+ */
+ public boolean resize(int width, int height, boolean fixAspect, boolean force) {
+ if (force || this.width != width || this.height != height) {
+ resize(width, height, fixAspect);
+ return true;
+ }
+ return false;
+ }
/**
* Returns the value of the bottom frustum
diff --git a/jme3-core/src/main/java/com/jme3/renderer/DefaultPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/DefaultPipelineContext.java
new file mode 100644
index 0000000000..2ca510f2d3
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/DefaultPipelineContext.java
@@ -0,0 +1,18 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.renderer;
+
+/**
+ *
+ * @author codex
+ */
+public class DefaultPipelineContext extends AbstractPipelineContext {
+
+ @Override
+ protected void beginRenderFrame(RenderManager rm) {}
+ @Override
+ protected void endRenderFrame(RenderManager rm) {}
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/DepthRange.java b/jme3-core/src/main/java/com/jme3/renderer/DepthRange.java
new file mode 100644
index 0000000000..c9ad3c705c
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/DepthRange.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2024 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.renderer;
+
+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 java.io.IOException;
+
+/**
+ * Defines a range within 0 and 1 that render depth values are clamped to.
+ *
+ * @author codex
+ */
+public class DepthRange implements Savable {
+
+ /**
+ * Range between 0 and 1.
+ */
+ public static final DepthRange IDENTITY = new DepthRange();
+ /**
+ * Range that clamps to zero.
+ */
+ public static final DepthRange FRONT = new DepthRange(0, 0);
+ /**
+ * Range that clamps to one.
+ */
+ public static final DepthRange REAR = new DepthRange(1, 1);
+
+ private float start, end;
+
+ /**
+ * Creates a new range between 0 and 1.
+ */
+ public DepthRange() {
+ set(0, 1);
+ }
+ /**
+ *
+ * @param start
+ * @param end
+ */
+ public DepthRange(float start, float end) {
+ set(start, end);
+ }
+ /**
+ *
+ * @param range
+ */
+ public DepthRange(DepthRange range) {
+ set(range);
+ }
+
+ /**
+ * Sets the range.
+ *
+ * @param start lower bound
+ * @param end upper bound
+ * @return this instance
+ */
+ public final DepthRange set(float start, float end) {
+ validateRange(start, end);
+ this.start = start;
+ this.end = end;
+ return this;
+ }
+
+ /**
+ * Sets the range.
+ *
+ * @param range
+ * @return this instance
+ */
+ public final DepthRange set(DepthRange range) {
+ // no need to validate range here
+ start = range.start;
+ end = range.end;
+ return this;
+ }
+
+ /**
+ * Sets the start (lower) bound.
+ *
+ * @param start
+ * @return this instance
+ */
+ public final DepthRange setStart(float start) {
+ validateRange(start, end);
+ this.start = start;
+ return this;
+ }
+
+ /**
+ * Sets the end (upper) bound.
+ *
+ * @param end
+ * @return
+ */
+ public final DepthRange setEnd(float end) {
+ validateRange(start, end);
+ this.end = end;
+ return this;
+ }
+
+ /**
+ * Gets the start (lower) bound.
+ *
+ * @return
+ */
+ public float getStart() {
+ return start;
+ }
+
+ /**
+ * Gets the end (upper) bound.
+ *
+ * @return
+ */
+ public float getEnd() {
+ return end;
+ }
+
+ private void validateRange(float start, float end) {
+ if (start > end) {
+ throw new IllegalStateException("Depth start cannot be beyond depth end.");
+ }
+ if (start > 1 || start < 0 || end > 1 || end < 0) {
+ throw new IllegalStateException("Depth parameters must be between 0 and 1 (inclusive).");
+ }
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == null || !(object instanceof DepthRange)) {
+ return false;
+ }
+ DepthRange obj = (DepthRange)object;
+ return start == obj.start && end == obj.end;
+ }
+
+ public boolean equals(DepthRange range) {
+ return range != null && start == range.start && end == range.end;
+ }
+
+ public boolean equals(float start, float end) {
+ return this.start != start && this.end != end;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 79 * hash + Float.floatToIntBits(this.start);
+ hash = 79 * hash + Float.floatToIntBits(this.end);
+ return hash;
+ }
+ @Override
+ public String toString() {
+ return "DepthRange["+start+" -> "+end+"]";
+ }
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule out = ex.getCapsule(this);
+ out.write(start, "start", 0);
+ out.write(end, "end", 1);
+ }
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ start = in.readFloat("start", 0);
+ end = in.readFloat("end", 1);
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/ForwardPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/ForwardPipeline.java
new file mode 100644
index 0000000000..85be771c1c
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/ForwardPipeline.java
@@ -0,0 +1,125 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.renderer;
+
+import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
+import com.jme3.profile.SpStep;
+import com.jme3.profile.VpStep;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SafeArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author codex
+ */
+public class ForwardPipeline implements RenderPipeline {
+
+ private boolean rendered = false;
+
+ @Override
+ public PipelineContext fetchPipelineContext(RenderManager rm) {
+ return rm.getDefaultContext();
+ }
+ @Override
+ public boolean hasRenderedThisFrame() {
+ return rendered;
+ }
+ @Override
+ public void beginRenderFrame(RenderManager rm) {}
+ @Override
+ public void pipelineRender(RenderManager rm, PipelineContext context, ViewPort vp, float tpf) {
+
+ AppProfiler prof = rm.getProfiler();
+
+ SafeArrayList processors = vp.getProcessors();
+ if (processors.isEmpty()) {
+ processors = null;
+ }
+
+ if (processors != null) {
+ if (prof != null) {
+ prof.vpStep(VpStep.PreFrame, vp, null);
+ }
+ for (SceneProcessor p : processors.getArray()) {
+ if (!p.isInitialized()) {
+ p.initialize(rm, vp);
+ }
+ p.setProfiler(prof);
+ if (prof != null) {
+ prof.spStep(SpStep.ProcPreFrame, p.getClass().getSimpleName());
+ }
+ p.preFrame(tpf);
+ }
+ }
+
+ rm.applyViewPort(vp);
+
+ if (prof != null) {
+ prof.vpStep(VpStep.RenderScene, vp, null);
+ }
+ // flatten scenes into render queue
+ List scenes = vp.getScenes();
+ for (int i = scenes.size() - 1; i >= 0; i--) {
+ rm.renderScene(scenes.get(i), vp);
+ }
+ if (processors != null) {
+ if (prof != null) {
+ prof.vpStep(VpStep.PostQueue, vp, null);
+ }
+ for (SceneProcessor p : processors.getArray()) {
+ if (prof != null) {
+ prof.spStep(SpStep.ProcPostQueue, p.getClass().getSimpleName());
+ }
+ p.postQueue(vp.getQueue());
+ }
+ }
+
+ if (prof != null) {
+ prof.vpStep(VpStep.FlushQueue, vp, null);
+ }
+ rm.flushQueue(vp);
+
+ if (processors != null) {
+ if (prof != null) {
+ prof.vpStep(VpStep.PostFrame, vp, null);
+ }
+ for (SceneProcessor proc : processors.getArray()) {
+ if (prof != null) {
+ prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName());
+ }
+ proc.postFrame(vp.getOutputFrameBuffer());
+ }
+ if (prof != null) {
+ prof.vpStep(VpStep.ProcEndRender, vp, null);
+ }
+ }
+
+ // render the translucent objects queue after processors have been rendered
+ rm.renderTranslucentQueue(vp);
+
+ // clear any remaining spatials that were not rendered.
+ rm.clearQueue(vp);
+
+ rendered = true;
+
+ /*
+ * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results
+ * if renderer#copyFrameBuffer is used later
+ */
+ rm.getRenderer().clearClipRect();
+
+ if (prof != null) {
+ prof.vpStep(VpStep.EndRender, vp, null);
+ }
+
+ }
+ @Override
+ public void endRenderFrame(RenderManager rm) {
+ rendered = false;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/GeometryRenderHandler.java b/jme3-core/src/main/java/com/jme3/renderer/GeometryRenderHandler.java
new file mode 100644
index 0000000000..c1f35b8cea
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/GeometryRenderHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2024 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.renderer;
+
+import com.jme3.scene.Geometry;
+
+/**
+ * Handles rendering of individual geometries.
+ *
+ * @author codex
+ */
+public interface GeometryRenderHandler {
+
+ public static final GeometryRenderHandler DEFAULT = (rm, geom) -> {
+ rm.renderGeometry(geom);
+ return true;
+ };
+
+ /**
+ * Renders the given geometry, or returns false.
+ *
+ * @param rm renderGeometry manager
+ * @param geom geometry to renderGeometry
+ * @return true if the geometry was rendered
+ */
+ public boolean renderGeometry(RenderManager rm, Geometry geom);
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/PipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/PipelineContext.java
new file mode 100644
index 0000000000..27007c3499
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/PipelineContext.java
@@ -0,0 +1,18 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.renderer;
+
+/**
+ * Handles objects globally for a single type of RenderPipeline.
+ *
+ * @author codex
+ */
+public interface PipelineContext {
+
+ public boolean addPipeline(RenderManager rm, RenderPipeline pipeline);
+
+ public void flushPipelineStack(RenderManager rm);
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
index df1c42f442..9aeeb8c8c3 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2024 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -46,6 +46,9 @@
import com.jme3.profile.AppStep;
import com.jme3.profile.SpStep;
import com.jme3.profile.VpStep;
+import com.jme3.renderer.framegraph.FrameGraph;
+import com.jme3.renderer.framegraph.RenderObjectMap;
+import com.jme3.renderer.framegraph.debug.GraphEventCapture;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.Bucket;
@@ -63,17 +66,22 @@
import com.jme3.system.Timer;
import com.jme3.texture.FrameBuffer;
import com.jme3.util.SafeArrayList;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * A high-level rendering interface that is
- * above the Renderer implementation. RenderManager takes care
- * of rendering the scene graphs attached to each viewport and
- * handling SceneProcessors.
+ * A high-level rendering interface that is above the Renderer implementation.
+ *
+ * RenderManager takes care of rendering the scene graphs attached to each
+ * viewport and handling SceneProcessors.
*
* @see SceneProcessor
* @see ViewPort
@@ -81,18 +89,21 @@
*/
public class RenderManager {
- private static final Logger logger = Logger.getLogger(RenderManager.class.getName());
private final Renderer renderer;
private final UniformBindingManager uniformBindingManager = new UniformBindingManager();
private final ArrayList preViewPorts = new ArrayList<>();
private final ArrayList viewPorts = new ArrayList<>();
private final ArrayList postViewPorts = new ArrayList<>();
+ private final HashMap contexts = new HashMap<>();
+ private final LinkedList usedContexts = new LinkedList<>();
+ private RenderPipeline defaultPipeline = new ForwardPipeline();
private Camera prevCam = null;
private Material forcedMaterial = null;
private String forcedTechnique = null;
private RenderState forcedRenderState = null;
private final SafeArrayList forcedOverrides
= new SafeArrayList<>(MatParamOverride.class);
+ private GeometryRenderHandler renderGeometry;
private int viewX;
private int viewY;
private int viewWidth;
@@ -104,10 +115,9 @@ public class RenderManager {
private LightFilter lightFilter = new DefaultLightFilter();
private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass;
private int singlePassLightBatchSize = 1;
- private MatParamOverride boundDrawBufferId=new MatParamOverride(VarType.Int,"BoundDrawBuffer",0);
+ private MatParamOverride boundDrawBufferId = new MatParamOverride(VarType.Int, "BoundDrawBuffer", 0);
private Predicate renderFilter;
-
/**
* Creates a high-level rendering interface over the
* low-level rendering interface.
@@ -117,6 +127,109 @@ public class RenderManager {
public RenderManager(Renderer renderer) {
this.renderer = renderer;
this.forcedOverrides.add(boundDrawBufferId);
+ contexts.put(PipelineContext.class, new DefaultPipelineContext());
+ }
+
+ /**
+ * Gets the default pipeline used when a ViewPort does not have a
+ * pipeline already assigned to it.
+ *
+ * @return
+ */
+ public RenderPipeline getPipeline() {
+ return defaultPipeline;
+ }
+
+ /**
+ * Sets the default pipeline used when a ViewPort does not have a
+ * pipeline already assigned to it.
+ *
+ * default={@link ForwardPipeline}
+ *
+ * @param pipeline default pipeline (not null)
+ */
+ public void setPipeline(RenderPipeline pipeline) {
+ this.defaultPipeline = pipeline;
+ }
+
+ /**
+ * Gets the default pipeline context registered under
+ * {@link PipelineContext#getClass()}.
+ *
+ * @return
+ */
+ public PipelineContext getDefaultContext() {
+ return getContext(PipelineContext.class);
+ }
+
+ /**
+ * Gets the pipeline context registered under the class.
+ *
+ * @param
+ * @param type
+ * @return
+ */
+ public T getContext(Class type) {
+ return (T)contexts.get(type);
+ }
+
+ /**
+ * Gets the pipeline context registered under the class or creates
+ * and registers a new context supplied by the supplier.
+ *
+ * @param
+ * @param type
+ * @param supplier
+ * @return
+ */
+ public T getOrCreateContext(Class type, Supplier supplier) {
+ T c = getContext(type);
+ if (c == null) {
+ c = supplier.get();
+ contexts.put(type, c);
+ }
+ return c;
+ }
+
+ /**
+ * Registers the pipeline context under the class.
+ *
+ * @param
+ * @param type
+ * @param context
+ */
+ public void registerContext(Class type, T context) {
+ contexts.put(type, context);
+ }
+
+ /**
+ * Gets the application profiler.
+ *
+ * @return
+ */
+ public AppProfiler getProfiler() {
+ return prof;
+ }
+
+ /**
+ * Sets the GeometryRenderHandler used to render geometry.
+ *
+ * default=null
+ *
+ * @param renderGeometry geometry render handler, or null to not use one for rendering
+ * @see GeometryRenderHandler
+ */
+ public void setGeometryRenderHandler(GeometryRenderHandler renderGeometry){
+ this.renderGeometry = renderGeometry;
+ }
+
+ /**
+ * Gest the GeometryRenderHandler used to render geometry.
+ *
+ * @return geometry render handler, or null of none is used for rendering
+ */
+ public GeometryRenderHandler getGeometryRenderHandler() {
+ return renderGeometry;
}
/**
@@ -402,7 +515,7 @@ public void notifyRescale(float x, float y) {
for (ViewPort vp : preViewPorts) {
notifyRescale(vp, x, y);
}
- for (ViewPort vp : viewPorts) {
+ for (ViewPort vp : viewPorts) {
notifyRescale(vp, x, y);
}
for (ViewPort vp : postViewPorts) {
@@ -422,6 +535,15 @@ public void notifyRescale(float x, float y) {
public void setForcedMaterial(Material mat) {
forcedMaterial = mat;
}
+
+ /**
+ * Gets the forced material.
+ *
+ * @return
+ */
+ public Material getForcedMaterial() {
+ return forcedMaterial;
+ }
/**
* Returns the forced render state previously set with
@@ -503,7 +625,7 @@ public void setForcedTechnique(String forcedTechnique) {
* material or any overrides that exist in the scene graph that have the
* same name.
*
- * @param override The override to add
+ * @param override The override to addUserEvent
* @see MatParamOverride
* @see #removeForcedMatParam(com.jme3.material.MatParamOverride)
*/
@@ -628,7 +750,33 @@ public void updateUniformBindings(Shader shader) {
* @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager)
*/
public void renderGeometry(Geometry geom) {
- if (renderFilter != null && !renderFilter.test(geom)) return;
+
+ if (renderFilter != null && !renderFilter.test(geom)) {
+ return;
+ }
+
+ LightList lightList = geom.getWorldLightList();
+ if (lightFilter != null) {
+ filteredLightList.clear();
+ lightFilter.filterLights(geom, filteredLightList);
+ lightList = filteredLightList;
+ }
+
+ renderGeometry(geom, lightList);
+
+ }
+
+ /**
+ *
+ * @param geom
+ * @param lightList
+ */
+ public void renderGeometry(Geometry geom, LightList lightList) {
+
+ if (renderFilter != null && !renderFilter.test(geom)) {
+ return;
+ }
+
this.renderer.pushDebugGroup(geom.getName());
if (geom.isIgnoreTransform()) {
setWorldMatrix(Matrix4f.IDENTITY);
@@ -636,23 +784,18 @@ public void renderGeometry(Geometry geom) {
setWorldMatrix(geom.getWorldMatrix());
}
- // Use material override to pass the current target index (used in api such as GL ES that do not support glDrawBuffer)
+ // Use material override to pass the current target index (used in api such as GL ES
+ // that do not support glDrawBuffer)
FrameBuffer currentFb = this.renderer.getCurrentFrameBuffer();
if (currentFb != null && !currentFb.isMultiTarget()) {
this.boundDrawBufferId.setValue(currentFb.getTargetIndex());
}
-
- // Perform light filtering if we have a light filter.
- LightList lightList = geom.getWorldLightList();
-
- if (lightFilter != null) {
- filteredLightList.clear();
- lightFilter.filterLights(geom, filteredLightList);
- lightList = filteredLightList;
- }
+
+ // updateFilterLight
+ geom.setFilterLight(lightList);
Material material = geom.getMaterial();
-
+
// If forcedTechnique exists, we try to force it for the render.
// If it does not exist in the mat def, we check for forcedMaterial and render the geom if not null.
// Otherwise, the geometry is not rendered.
@@ -736,7 +879,7 @@ public void preloadScene(Spatial scene) {
preloadScene(children.get(i));
}
} else if (scene instanceof Geometry) {
- // add to the render queue
+ // addUserEvent to the render queue
Geometry gm = (Geometry) scene;
if (gm.getMaterial() == null) {
throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
@@ -755,7 +898,7 @@ public void preloadScene(Spatial scene) {
}
}
}
-
+
/**
* Flattens the given scene graph into the ViewPort's RenderQueue,
* checking for culling as the call goes down the graph recursively.
@@ -786,11 +929,11 @@ public void preloadScene(Spatial scene) {
* contain the flattened scene graph.
*/
public void renderScene(Spatial scene, ViewPort vp) {
- //reset of the camera plane state for proper culling
- //(must be 0 for the first note of the scene to be rendered)
+ // reset of the camera plane state for proper culling
+ // (must be 0 for the first note of the scene to be rendered)
vp.getCamera().setPlaneState(0);
- //rendering the scene
- renderSubScene(scene, vp);
+ // queue the scene for rendering
+ queueSubScene(scene, vp);
}
/**
@@ -799,13 +942,11 @@ public void renderScene(Spatial scene, ViewPort vp) {
* @param scene the scene to be rendered (not null)
* @param vp the ViewPort to render in (not null)
*/
- private void renderSubScene(Spatial scene, ViewPort vp) {
-
- // check culling first.
+ private void queueSubScene(Spatial scene, ViewPort vp) {
+ // check culling first
if (!scene.checkCulling(vp.getCamera())) {
return;
}
-
scene.runControlRender(this, vp);
if (scene instanceof Node) {
// Recurse for all children
@@ -816,15 +957,14 @@ private void renderSubScene(Spatial scene, ViewPort vp) {
for (int i = 0; i < children.size(); i++) {
// Restoring cam state before proceeding children recursively
vp.getCamera().setPlaneState(camState);
- renderSubScene(children.get(i), vp);
+ queueSubScene(children.get(i), vp);
}
} else if (scene instanceof Geometry) {
- // add to the render queue
+ // addUserEvent to the render queue
Geometry gm = (Geometry) scene;
if (gm.getMaterial() == null) {
throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
}
-
vp.getQueue().addToQueue(gm, scene.getQueueBucket());
}
}
@@ -1038,7 +1178,7 @@ public void renderTranslucentQueue(ViewPort vp) {
}
private void setViewPort(Camera cam) {
- // this will make sure to update viewport only if needed
+ // this will make sure to clearReservations viewport only if needed
if (cam != prevCam || cam.isViewportChanged()) {
viewX = (int) (cam.getViewPortLeft() * cam.getWidth());
viewY = (int) (cam.getViewPortBottom() * cam.getHeight());
@@ -1119,6 +1259,23 @@ public void renderViewPortRaw(ViewPort vp) {
flushQueue(vp);
}
+ /**
+ * Applies the ViewPort's Camera and FrameBuffer in preparation
+ * for rendering.
+ *
+ * @param vp
+ */
+ public void applyViewPort(ViewPort vp) {
+ renderer.setFrameBuffer(vp.getOutputFrameBuffer());
+ setCamera(vp.getCamera(), false);
+ if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) {
+ if (vp.isClearColor()) {
+ renderer.setBackgroundColor(vp.getBackgroundColor());
+ }
+ renderer.clearBuffers(vp.isClearColor(), vp.isClearDepth(), vp.isClearStencil());
+ }
+ }
+
/**
* Renders the {@link ViewPort}.
*
@@ -1165,96 +1322,16 @@ public void renderViewPortRaw(ViewPort vp) {
public void renderViewPort(ViewPort vp, float tpf) {
if (!vp.isEnabled()) {
return;
+ }
+ RenderPipeline pipeline = vp.getFrameGraph();
+ if (pipeline == null) {
+ pipeline = defaultPipeline;
}
- if (prof != null) {
- prof.vpStep(VpStep.BeginRender, vp, null);
- }
-
- SafeArrayList processors = vp.getProcessors();
- if (processors.isEmpty()) {
- processors = null;
- }
-
- if (processors != null) {
- if (prof != null) {
- prof.vpStep(VpStep.PreFrame, vp, null);
- }
- for (SceneProcessor proc : processors.getArray()) {
- if (!proc.isInitialized()) {
- proc.initialize(this, vp);
- }
- proc.setProfiler(this.prof);
- if (prof != null) {
- prof.spStep(SpStep.ProcPreFrame, proc.getClass().getSimpleName());
- }
- proc.preFrame(tpf);
- }
- }
-
- renderer.setFrameBuffer(vp.getOutputFrameBuffer());
- setCamera(vp.getCamera(), false);
- if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) {
- if (vp.isClearColor()) {
- renderer.setBackgroundColor(vp.getBackgroundColor());
- }
- renderer.clearBuffers(vp.isClearColor(),
- vp.isClearDepth(),
- vp.isClearStencil());
- }
-
- if (prof != null) {
- prof.vpStep(VpStep.RenderScene, vp, null);
- }
- List scenes = vp.getScenes();
- for (int i = scenes.size() - 1; i >= 0; i--) {
- renderScene(scenes.get(i), vp);
- }
-
- if (processors != null) {
- if (prof != null) {
- prof.vpStep(VpStep.PostQueue, vp, null);
- }
- for (SceneProcessor proc : processors.getArray()) {
- if (prof != null) {
- prof.spStep(SpStep.ProcPostQueue, proc.getClass().getSimpleName());
- }
- proc.postQueue(vp.getQueue());
- }
- }
-
- if (prof != null) {
- prof.vpStep(VpStep.FlushQueue, vp, null);
- }
- flushQueue(vp);
-
- if (processors != null) {
- if (prof != null) {
- prof.vpStep(VpStep.PostFrame, vp, null);
- }
- for (SceneProcessor proc : processors.getArray()) {
- if (prof != null) {
- prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName());
- }
- proc.postFrame(vp.getOutputFrameBuffer());
- }
- if (prof != null) {
- prof.vpStep(VpStep.ProcEndRender, vp, null);
- }
- }
- //renders the translucent objects queue after processors have been rendered
- renderTranslucentQueue(vp);
- // clear any remaining spatials that were not rendered.
- clearQueue(vp);
-
- /*
- * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results
- * if renderer#copyFrameBuffer is used later
- */
- renderer.clearClipRect();
-
- if (prof != null) {
- prof.vpStep(VpStep.EndRender, vp, null);
+ PipelineContext context = pipeline.fetchPipelineContext(this);
+ if (context.addPipeline(this, pipeline)) {
+ usedContexts.add(context);
}
+ pipeline.pipelineRender(this, context, vp, tpf);
}
/**
@@ -1276,7 +1353,7 @@ public void render(float tpf, boolean mainFrameBufferActive) {
if (renderer instanceof NullRenderer) {
return;
}
-
+
uniformBindingManager.newFrame();
if (prof != null) {
@@ -1308,12 +1385,20 @@ public void render(float tpf, boolean mainFrameBufferActive) {
renderViewPort(vp, tpf);
}
}
+
+ // cleanup for used pipeline contexts only
+ System.out.println("flush pipelines");
+ for (PipelineContext c : usedContexts) {
+ System.out.println("flush pipeline stack for "+c);
+ c.flushPipelineStack(this);
+ }
+ usedContexts.clear();
+
}
-
/**
* Returns true if the draw buffer target id is passed to the shader.
- *
+ *
* @return True if the draw buffer target id is passed to the shaders.
*/
public boolean getPassDrawBufferTargetIdToShaders() {
@@ -1324,7 +1409,7 @@ public boolean getPassDrawBufferTargetIdToShaders() {
* Enable or disable passing the draw buffer target id to the shaders. This
* is needed to handle FrameBuffer.setTargetIndex correctly in some
* backends.
- *
+ *
* @param v
* True to enable, false to disable (default is true)
*/
@@ -1337,11 +1422,12 @@ public void setPassDrawBufferTargetIdToShaders(boolean v) {
this.forcedOverrides.remove(boundDrawBufferId);
}
}
+
/**
* Set a render filter. Every geometry will be tested against this filter
* before rendering and will only be rendered if the filter returns true.
*
- * @param filter
+ * @param filter the render filter
*/
public void setRenderFilter(Predicate filter) {
renderFilter = filter;
@@ -1350,7 +1436,7 @@ public void setRenderFilter(Predicate filter) {
/**
* Returns the render filter that the RenderManager is currently using
*
- * @return the render filter
+ * @return the render filter
*/
public Predicate getRenderFilter() {
return renderFilter;
diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/RenderPipeline.java
new file mode 100644
index 0000000000..3aa658016a
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/RenderPipeline.java
@@ -0,0 +1,58 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.renderer;
+
+/**
+ * Renders a ViewPort.
+ *
+ * @author codex
+ * @param
+ */
+public interface RenderPipeline {
+
+ /**
+ * Fetches the PipelineContext this pipeline requires for rendering
+ * from the RenderManager.
+ *
+ * @param rm
+ * @return pipeline context (not null)
+ */
+ public T fetchPipelineContext(RenderManager rm);
+
+ /**
+ * Returns true if this pipeline has rendered a viewport this render frame.
+ *
+ * @return
+ */
+ public boolean hasRenderedThisFrame();
+
+ /**
+ * Called before this pipeline is rendered for the first time this frame.
+ *
+ * Only called if the pipeline will actually be rendered.
+ *
+ * @param rm
+ */
+ public void beginRenderFrame(RenderManager rm);
+
+ /**
+ * Renders the pipeline.
+ *
+ * @param rm
+ * @param context
+ * @param vp
+ * @param tpf
+ */
+ public void pipelineRender(RenderManager rm, T context, ViewPort vp, float tpf);
+
+ /**
+ * Called after all rendering is complete in a rendering frame this
+ * pipeline participated in.
+ *
+ * @param rm
+ */
+ public void endRenderFrame(RenderManager rm);
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java
index c92de923ae..f553980a8f 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java
@@ -128,6 +128,16 @@ public interface Renderer {
* @param end The range end
*/
public void setDepthRange(float start, float end);
+
+ /**
+ * Sets the range of depth values for objects.
+ *
+ * @param range
+ * @see #setDepthRange(float, float)
+ */
+ public default void setDepthRange(DepthRange range) {
+ setDepthRange(range.getStart(), range.getEnd());
+ }
/**
* Called when a new frame has been rendered.
diff --git a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java
index 6f0424fb30..3756dfb588 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2024 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -33,6 +33,7 @@
import com.jme3.math.ColorRGBA;
import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.framegraph.FrameGraph;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
@@ -84,6 +85,10 @@ public class ViewPort {
* Scene processors currently applied.
*/
protected final SafeArrayList processors = new SafeArrayList<>(SceneProcessor.class);
+ /**
+ * Dedicated framegraph.
+ */
+ protected FrameGraph framegraph;
/**
* FrameBuffer for output.
*/
@@ -424,5 +429,29 @@ public void setEnabled(boolean enable) {
public boolean isEnabled() {
return enabled;
}
+
+ /**
+ * Sets the framegraph used by this viewport for rendering.
+ *
+ * If null, the render manager's default framegraph, if not null, will be
+ * used to render this viewport. If all else fails, the default forward
+ * renderer will be used.
+ *
+ * default=null
+ *
+ * @param framegraph framegraph, or null
+ */
+ public void setFrameGraph(FrameGraph framegraph) {
+ this.framegraph = framegraph;
+ }
+
+ /**
+ * Gets the framegraph used by this viewport for rendering.
+ *
+ * @return
+ */
+ public FrameGraph getFrameGraph() {
+ return framegraph;
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/Connectable.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/Connectable.java
new file mode 100644
index 0000000000..6ec9f5c812
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/Connectable.java
@@ -0,0 +1,75 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template
+ */
+package com.jme3.renderer.framegraph;
+
+/**
+ *
+ * @author codex
+ */
+public interface Connectable {
+
+ public ResourceTicket getInput(String name);
+
+ public ResourceTicket getOutput(String name);
+
+ public TicketGroup getGroup(String name);
+
+ public ResourceTicket addListEntry(String groupName);
+
+ public default ResourceTicket getInput(String name, boolean failOnMiss) {
+ ResourceTicket t = getInput(name);
+ if (t == null && failOnMiss) {
+ throw new NullPointerException("Input ticket \""+name+"\" does not exist.");
+ }
+ return t;
+ }
+
+ public default ResourceTicket getOutput(String name, boolean failOnMiss) {
+ ResourceTicket t = getOutput(name);
+ if (t == null && failOnMiss) {
+ throw new NullPointerException("Output ticket \""+name+"\" does not exist.");
+ }
+ return t;
+ }
+
+ public default TicketGroup getGroup(String name, boolean failOnMiss) {
+ TicketGroup g = getGroup(name);
+ if (g == null && failOnMiss) {
+ throw new NullPointerException("Group \""+name+"\" does not exist.");
+ }
+ return g;
+ }
+
+ public default void makeInput(Connectable source, String sourceTicket, String targetTicket) {
+ ResourceTicket out = source.getOutput(sourceTicket, true);
+ if (TicketGroup.isListTicket(targetTicket)) {
+ ResourceTicket t = addListEntry(TicketGroup.extractGroupName(targetTicket));
+ t.setSource(out);
+ } else {
+ ResourceTicket target = getInput(targetTicket, true);
+ target.setSource(out);
+ }
+ }
+
+ public default void makeGroupInput(Connectable source, String sourceGroup, String targetGroup, int sourceStart, int targetStart, int length) {
+ ResourceTicket[] sourceArray = source.getGroup(sourceGroup, true).getArray();
+ ResourceTicket[] targetArray = getGroup(targetGroup, true).getArray();
+ int n = Math.min(sourceStart+length, sourceArray.length);
+ int m = Math.min(targetStart+length, targetArray.length);
+ for (; sourceStart < n && targetStart < m; sourceStart++, targetStart++) {
+ targetArray[targetStart].setSource(sourceArray[sourceStart]);
+ }
+ }
+
+ public default void makeGroupInput(Connectable source, String sourceGroup, String targetGroup) {
+ makeGroupInput(source, sourceGroup, targetGroup, 0, 0, Integer.MAX_VALUE);
+ }
+
+ public default void makeInputToList(Connectable source, String sourceTicket, String targetGroup) {
+ ResourceTicket t = addListEntry(targetGroup);
+ t.setSource(source.getOutput(sourceTicket));
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPipelineContext.java
new file mode 100644
index 0000000000..aeabc75a22
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPipelineContext.java
@@ -0,0 +1,64 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.renderer.framegraph;
+
+import com.jme3.renderer.AbstractPipelineContext;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.framegraph.debug.GraphEventCapture;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Manages global pipeline context for rendering with FrameGraphs.
+ *
+ * @author codex
+ */
+public class FGPipelineContext extends AbstractPipelineContext {
+
+ private static final Logger LOG = Logger.getLogger(FGPipelineContext.class.getName());
+
+ private final RenderObjectMap renderObjects;
+ private GraphEventCapture eventCapture;
+
+ public FGPipelineContext(RenderManager rm) {
+ renderObjects = new RenderObjectMap(this, true);
+ }
+
+ @Override
+ public void beginRenderFrame(RenderManager rm) {
+ if (eventCapture != null) {
+ eventCapture.beginRenderFrame();
+ }
+ renderObjects.newFrame();
+ }
+ @Override
+ public void endRenderFrame(RenderManager rm) {
+ if (eventCapture != null) {
+ eventCapture.endRenderFrame();
+ }
+ renderObjects.flushMap();
+ if (eventCapture != null && eventCapture.isComplete()) {
+ try {
+ eventCapture.export();
+ } catch (IOException ex) {
+ LOG.log(Level.SEVERE, "Error exporting captured event data.", ex);
+ }
+ eventCapture = null;
+ }
+ }
+
+ public void setEventCapture(GraphEventCapture eventCapture) {
+ this.eventCapture = eventCapture;
+ }
+
+ public RenderObjectMap getRenderObjects() {
+ return renderObjects;
+ }
+ public GraphEventCapture getEventCapture() {
+ return eventCapture;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java
new file mode 100644
index 0000000000..2987c0279c
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2024 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.renderer.framegraph;
+
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.opencl.CommandQueue;
+import com.jme3.opencl.Context;
+import com.jme3.profile.AppProfiler;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.framegraph.debug.GraphEventCapture;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Texture2D;
+import java.util.function.Predicate;
+import com.jme3.renderer.GeometryRenderHandler;
+import com.jme3.renderer.RendererException;
+
+/**
+ * Context for FrameGraph rendering.
+ *
+ * Provides RenderPasses with access to important objects such as the RenderManager,
+ * ViewPort, profiler, and fullscreen quad. Utility methods are provided for
+ * fullscreen quad rendering and camera management.
+ *
+ * Additionally, the following render settings are handled to ensure settings
+ * do not leak between renders.
+ *
+ *
Forced technique
+ *
Forced material
+ *
Geometry render handler
+ *
Geometry filter
+ *
Forced render state
+ *
+ * After each pass execution on the main render thread, {@link #popRenderSettings()} is
+ * called to reset these settings to what they were before rendering began.
+ *
+ * @author codex
+ */
+public class FGRenderContext {
+
+ private final FrameGraph frameGraph;
+ private RenderManager renderManager;
+ private FGPipelineContext context;
+ private ViewPort viewPort;
+ private AppProfiler profiler;
+ private float tpf;
+ private final FullScreenQuad screen;
+ private Context clContext;
+ private CommandQueue clQueue;
+
+ private String forcedTechnique;
+ private Material forcedMat;
+ private FrameBuffer frameBuffer;
+ private GeometryRenderHandler geomRender;
+ private Predicate geomFilter;
+ private RenderState renderState;
+ private int camWidth, camHeight;
+
+ public FGRenderContext(FrameGraph frameGraph) {
+ this(frameGraph, null);
+ }
+ public FGRenderContext(FrameGraph frameGraph, Context clContext) {
+ this.frameGraph = frameGraph;
+ this.clContext = clContext;
+ this.screen = new FullScreenQuad(this.frameGraph.getAssetManager());
+ }
+
+ /**
+ * Targets this context to the viewport.
+ *
+ * @param rm
+ * @param context
+ * @param vp
+ * @param profiler
+ * @param tpf
+ */
+ public void target(RenderManager rm, FGPipelineContext context, ViewPort vp, AppProfiler profiler, float tpf) {
+ this.renderManager = rm;
+ this.context = context;
+ this.viewPort = vp;
+ this.profiler = profiler;
+ this.tpf = tpf;
+ if (viewPort == null) {
+ throw new NullPointerException("ViewPort cannot be null.");
+ }
+ }
+ /**
+ * Returns true if the context is ready for rendering.
+ *
+ * @return
+ */
+ public boolean isReady() {
+ return renderManager != null && viewPort != null;
+ }
+
+ /**
+ * Saves the current render settings.
+ */
+ public void pushRenderSettings() {
+ forcedTechnique = renderManager.getForcedTechnique();
+ forcedMat = renderManager.getForcedMaterial();
+ frameBuffer = renderManager.getRenderer().getCurrentFrameBuffer();
+ geomRender = renderManager.getGeometryRenderHandler();
+ geomFilter = renderManager.getRenderFilter();
+ renderState = renderManager.getForcedRenderState();
+ camWidth = viewPort.getCamera().getWidth();
+ camHeight = viewPort.getCamera().getHeight();
+ }
+ /**
+ * Applies saved render settings, except the framebuffer.
+ */
+ public void popRenderSettings() {
+ renderManager.setForcedTechnique(forcedTechnique);
+ renderManager.setForcedMaterial(forcedMat);
+ renderManager.getRenderer().setFrameBuffer(frameBuffer);
+ renderManager.setGeometryRenderHandler(geomRender);
+ renderManager.setRenderFilter(geomFilter);
+ renderManager.setForcedRenderState(renderState);
+ renderManager.getRenderer().setDepthRange(0, 1);
+ resizeCamera(camWidth, camHeight, true, false, false);
+ if (viewPort.isClearColor()) {
+ renderManager.getRenderer().setBackgroundColor(viewPort.getBackgroundColor());
+ }
+ }
+ /**
+ * Applies the saved framebuffer.
+ */
+ public void popFrameBuffer() {
+ renderManager.getRenderer().setFrameBuffer(frameBuffer);
+ }
+
+ /**
+ * Renders the given geometry list with the camera and render handler.
+ *
+ * @param queue queue of geometry to render (not null)
+ * @param cam camera to render with (or null to render with the current viewport camera)
+ * @param handler handler to render with (or null to render with {@link GeometryRenderHandler#DEFAULT})
+ */
+ public void renderGeometry(GeometryQueue queue, Camera cam, GeometryRenderHandler handler) {
+ if (cam == null) {
+ cam = viewPort.getCamera();
+ }
+ queue.setCamera(cam);
+ queue.sort();
+ queue.render(renderManager, handler);
+ }
+ /**
+ * Renders the material on a fullscreen quad.
+ *
+ * @param mat
+ */
+ public void renderFullscreen(Material mat) {
+ screen.render(renderManager, mat);
+ }
+ /**
+ * Renders the color and depth textures on a fullscreen quad, where
+ * the color texture informs the color, and the depth texture informs
+ * the depth.
+ *
+ * If both color and depth are null, no rendering will be performed
+ *
+ * @param color color texture, or null
+ * @param depth depth texture, or null
+ */
+ public void renderTextures(Texture2D color, Texture2D depth) {
+ if (color != null) {
+ //resizeCamera(color.getImage().getWidth(), color.getImage().getHeight(), false, false);
+ } else if (depth != null) {
+ //resizeCamera(depth.getImage().getWidth(), depth.getImage().getHeight(), false, false);
+ }
+ screen.render(renderManager, color, depth);
+ }
+
+ /**
+ * Resizes the camera to the width and height.
+ *
+ * @param w new camera width
+ * @param h new camera height
+ * @param fixAspect true to fix camera aspect
+ * @param ortho true to use parallel projection
+ * @param force true to force setting the width and height
+ */
+ public void resizeCamera(int w, int h, boolean fixAspect, boolean ortho, boolean force) {
+ Camera cam = viewPort.getCamera();
+ if (cam.resize(w, h, fixAspect, force)) {
+ renderManager.setCamera(cam, ortho);
+ }
+ }
+
+ /**
+ * Sets the OpenCL context for compute shading.
+ *
+ * @param clContext
+ */
+ public void setCLContext(Context clContext) {
+ this.clContext = clContext;
+ }
+ /**
+ * Sets the OpenCL command queue for compute shading.
+ *
+ * @param clQueue
+ */
+ public void setCLQueue(CommandQueue clQueue) {
+ this.clQueue = clQueue;
+ }
+
+ /**
+ * Gets the resource list belonging to the framegraph.
+ *
+ * @return
+ */
+ public ResourceList getResources() {
+ return frameGraph.getResources();
+ }
+ /**
+ * Gets the render manager.
+ *
+ * @return
+ */
+ public RenderManager getRenderManager() {
+ return renderManager;
+ }
+ /**
+ * Gets the context for the FrameGraph pipeline.
+ *
+ * @return
+ */
+ public FGPipelineContext getPipelineContext() {
+ return context;
+ }
+ /**
+ * Gets the viewport currently being rendered.
+ *
+ * @return
+ */
+ public ViewPort getViewPort() {
+ return viewPort;
+ }
+ /**
+ * Gets the profiler.
+ *
+ * @return app profiler, or null
+ */
+ public AppProfiler getProfiler() {
+ return profiler;
+ }
+ /**
+ * Gets the renderer held by the render manager.
+ *
+ * @return
+ */
+ public Renderer getRenderer() {
+ return renderManager.getRenderer();
+ }
+ /**
+ * Gets the render queue held by the viewport.
+ *
+ * @return
+ */
+ public RenderQueue getRenderQueue() {
+ if (viewPort != null) {
+ return viewPort.getQueue();
+ } else {
+ return null;
+ }
+ }
+ /**
+ * Gets the fullscreen quad used for fullscreen renders.
+ *
+ * @return
+ */
+ public FullScreenQuad getScreen() {
+ return screen;
+ }
+ /**
+ * Gets the debug frame capture if one is assigned.
+ *
+ * @return
+ */
+ public GraphEventCapture getGraphCapture() {
+ return context.getEventCapture();
+ }
+ /**
+ * Gets the OpenCL context for compute shading.
+ *
+ * @return
+ */
+ public Context getCLContext() {
+ return clContext;
+ }
+ /**
+ * Gets the OpenCL command queue assigned to this context.
+ *
+ * @return
+ */
+ public CommandQueue getCLQueue() {
+ return clQueue;
+ }
+ /**
+ * Gets the time per frame.
+ *
+ * @return
+ */
+ public float getTpf() {
+ return tpf;
+ }
+ /**
+ * Gets the camera width.
+ *
+ * @return
+ */
+ public int getWidth() {
+ return viewPort.getCamera().getWidth();
+ }
+ /**
+ * Gets the camera height.
+ *
+ * @return
+ */
+ public int getHeight() {
+ return viewPort.getCamera().getHeight();
+ }
+ /**
+ * Returns true if the FrameGraph is asynchronous.
+ *
+ * @return
+ */
+ public boolean isAsync() {
+ return frameGraph.isAsync();
+ }
+
+ /**
+ * Returns true if the app profiler is not null.
+ *
+ * @return
+ */
+ public boolean isProfilerAvailable() {
+ return profiler != null;
+ }
+ /**
+ * Returns true if a debug frame snapshot is assigned.
+ *
+ * @return
+ */
+ public boolean isGraphCaptureActive() {
+ return context.getEventCapture() != null;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java
new file mode 100644
index 0000000000..f21f910af5
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java
@@ -0,0 +1,623 @@
+/*
+ * Copyright (c) 2024 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.renderer.framegraph;
+
+import com.jme3.renderer.framegraph.modules.ModuleLocator;
+import com.jme3.renderer.framegraph.modules.RenderThread;
+import com.jme3.renderer.framegraph.modules.ThreadLauncher;
+import com.jme3.renderer.framegraph.modules.RenderModule;
+import com.jme3.renderer.framegraph.export.ModuleGraphData;
+import com.jme3.renderer.framegraph.passes.RenderPass;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.FrameGraphKey;
+import com.jme3.export.SavableObject;
+import com.jme3.opencl.CommandQueue;
+import com.jme3.opencl.Context;
+import com.jme3.profile.AppProfiler;
+import com.jme3.profile.VpStep;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.RenderPipeline;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.framegraph.client.GraphSetting;
+import com.jme3.renderer.framegraph.debug.GraphEventCapture;
+import com.jme3.renderer.framegraph.export.FrameGraphData;
+import java.util.HashMap;
+import java.util.function.Function;
+import java.util.logging.Logger;
+
+/**
+ * Manages render passes, dependencies, and resources in a node-based parameter system.
+ *
+ * Rendering is a complicated task, involving many parameters and resources. The framegraph
+ * aims to simplify rendering from the user's perspective, and limit the creation, binding,
+ * and destruction of resources wherever possible.
+ *
+ * Passes are expected to declare and describe beforehand the resources they plan on using
+ * during execution. Passes can also reference resources declared by other passes. The resource
+ * manager can determine from these "promises" which passes can be culled, as their contributions
+ * would ultimately go unused.
+ *
+ * During execution, passes expected ask the resource manager for the resource the declared or referenced
+ * earlier. If the resource does not already exist (is virtual) the manager will either create a new
+ * resource or allocate an existing, unused resource that qualifies based on the description provided
+ * on declaration. Reallocation is usually preferred to reduce memory footprint.
+ *
+ * FrameGraph execution occurs in four steps:
+ *
+ *
Preparation. Passes declare, reserve, and reference resources
+ * during this step.
+ *
Culling. The resource manager determines which resources and
+ * passes are unused, and culls them. This can often save loads of resources, as many
+ * passes may not used for large parts of the application.
+ *
Execution. Passes that were not culled acquire the resources
+ * they need, and perform rendering operations. All passes are expected to release
+ * all resources they declared or referenced in the first step, however, this is done
+ * automatically by {@link RenderPass}.
+ *
Reset. Passes perform whatever post-rendering cleanup is necessary.
+ *
+ *
+ * Each step begins only after every qualifying pass has completed the previous step.
+ *
+ * Passes are executed in the order they appear in the queue. This can sometimes lead
+ * to unintended consequences, as a pass may use resources generated by a later queue.
+ *
+ * @author codex
+ */
+public class FrameGraph implements RenderPipeline {
+
+ private static final Logger LOG = Logger.getLogger(FrameGraph.class.getName());
+
+ private final AssetManager assetManager;
+ private final ResourceList resources;
+ private final FGRenderContext context;
+ private final HashMap settings = new HashMap<>();
+ private ThreadLauncher launcher = new ThreadLauncher();
+ private String name = "FrameGraph";
+ private String docAsset = null;
+ private boolean rendered = false;
+ private boolean debugPrint = false;
+ private boolean interrupted = false;
+ private int nextModuleId = 0;
+
+ /**
+ * Creates a new blank framegraph.
+ *
+ * @param assetManager asset manager (not null)
+ */
+ public FrameGraph(AssetManager assetManager) {
+ this.assetManager = assetManager;
+ this.resources = new ResourceList(this);
+ this.context = new FGRenderContext(this);
+ launcher.initializeModule(this);
+ launcher.getOrCreate(PassIndex.MAIN_THREAD);
+ }
+ /**
+ * Creates a new framegraph from the given data.
+ *
+ * @param assetManager
+ * @param data
+ */
+ public FrameGraph(AssetManager assetManager, FrameGraphData data) {
+ this(assetManager);
+ applyData(data);
+ }
+ /**
+ * Creates a new framegraph from data obtained by the given asset key.
+ *
+ * @param assetManager
+ * @param key
+ */
+ public FrameGraph(AssetManager assetManager, FrameGraphKey key) {
+ this(assetManager, assetManager.loadFrameGraph(key));
+ }
+ /**
+ * Creates a new framegraph from data obtained by the given asset name.
+ *
+ * @param assetManager
+ * @param dataAsset
+ */
+ public FrameGraph(AssetManager assetManager, String dataAsset) {
+ this(assetManager, assetManager.loadFrameGraph(dataAsset));
+ }
+
+ @Override
+ public FGPipelineContext fetchPipelineContext(RenderManager rm) {
+ return rm.getOrCreateContext(FGPipelineContext.class, () -> new FGPipelineContext(rm));
+ }
+ @Override
+ public void beginRenderFrame(RenderManager rm) {}
+ @Override
+ public void pipelineRender(RenderManager rm, FGPipelineContext pContext, ViewPort vp, float tpf) {
+
+ if (interrupted) {
+ throw new IllegalStateException("Cannot render because FrameGraph crashed.");
+ }
+
+ AppProfiler prof = rm.getProfiler();
+ if (prof != null) {
+ prof.vpStep(VpStep.BeginRender, vp, null);
+ }
+
+ // prepare
+ rm.applyViewPort(vp);
+ context.target(rm, pContext, vp, prof, tpf);
+ GraphEventCapture cap = context.getGraphCapture();
+ if (cap != null) {
+ cap.renderViewPort(context.getViewPort());
+ }
+ if (prof != null) prof.vpStep(VpStep.FrameGraphSetup, vp, null);
+ if (!rendered) {
+ resources.beginRenderFrame(
+ pContext.getRenderObjects(), pContext.getEventCapture());
+ }
+ launcher.prepareModuleRender(context, new PassIndex(0, 0));
+ resources.applyFutureReferences();
+
+ // cull modules and resources
+ if (prof != null) prof.vpStep(VpStep.FrameGraphCull, vp, null);
+ launcher.countReferences();
+ resources.cullUnreferenced();
+
+ // execute
+ if (prof != null) prof.vpStep(VpStep.FrameGraphExecute, vp, null);
+ context.pushRenderSettings();
+ // execute threads in reverse order so that the 0th thread is executed
+ // last on JME's main thread.
+ launcher.executeModuleRender(context);
+ if (interrupted) {
+ throw new RuntimeException("FrameGraph execution was interrupted.");
+ }
+ context.popFrameBuffer();
+
+ // reset
+ if (prof != null) prof.vpStep(VpStep.FrameGraphReset, vp, null);
+ launcher.resetRender(context);
+ pContext.getRenderObjects().clearReservations();
+ resources.clear();
+ rm.getRenderer().clearClipRect();
+ rendered = true;
+
+ if (prof != null) {
+ prof.vpStep(VpStep.EndRender, vp, null);
+ }
+
+ }
+ @Override
+ public boolean hasRenderedThisFrame() {
+ return rendered;
+ }
+ @Override
+ public void endRenderFrame(RenderManager rm) {
+ launcher.renderingComplete();
+ rendered = false;
+ }
+ @Override
+ public String toString() {
+ return "FrameGraph ("+name+")";
+ }
+
+ /**
+ * Adds the pass to end of the {@link PassThread} running on the main render thread.
+ *
+ * @param
+ * @param module
+ * @return given pass
+ */
+ public T add(T module) {
+ return launcher.getOrCreate(PassIndex.MAIN_THREAD).add(module);
+ }
+ /**
+ * Adds the pass at the index.
+ *
+ * If the thread index is >= the total number of {@link PassThreads}s,
+ * a new PassThread will be created for this to be added to.
+ *
+ * If the queue index is >= the current queue size, the pass will
+ * be added to the end of the queue. Passes above the added pass
+ * will have their indexes shifted.
+ *
+ * @param
+ * @param pass
+ * @param index
+ * @return
+ */
+ public T add(T pass, PassIndex index) {
+ return launcher.getOrCreate(index.getThreadIndex()).add(pass, index.queueIndex);
+ }
+
+ /**
+ * Adds an array of passes connected in series to the framegraph.
+ *
+ * The named input ticket on each pass (except the first) is connected to
+ * the named output ticket on the previous pass, creating a series of connected
+ * passes. The array length determines the number of passes that will be added
+ * and connected.
+ *
+ * Null elements of the array are replaced using the Function.
+ *
+ * @param
+ * @param array array of passes (elements may be null)
+ * @param factory creates passes where array elements are null (may be null)
+ * @param inTicket name of the input ticket on each pass
+ * @param outTicket name of the output ticket on each pass
+ * @return array of passes
+ * @see PassThread#addLoop(T[], int, java.util.function.Supplier, java.lang.String, java.lang.String)
+ */
+ public T[] addLoop(T[] array, Function factory,
+ String inTicket, String outTicket) {
+ return launcher.getOrCreate(PassIndex.MAIN_THREAD).addLoop(array, -1, factory, inTicket, outTicket);
+ }
+ /**
+ * Adds an array of passes connected in series to the framegraph.
+ *
+ * The named input ticket on each pass (except the first) is connected to
+ * the named output ticket on the previous pass.
+ *
+ * @param
+ * @param array array of passes (elements may be null)
+ * @param index index that passes are added to
+ * @param function creates passes where array elements are null (may be null)
+ * @param inTicket name of the input ticket on each pass
+ * @param outTicket name of the output ticket on each pass
+ * @return array of passes
+ * @see PassThread#addLoop(T[], int, java.util.function.Supplier, java.lang.String, java.lang.String)
+ */
+ public T[] addLoop(T[] array, PassIndex index,
+ Function function, String inTicket, String outTicket) {
+ return launcher.getOrCreate(index.getThreadIndex()).addLoop(
+ array, index.queueIndex, function, inTicket, outTicket);
+ }
+
+ /**
+ * Gets the first pass that qualifies.
+ *
+ * @param
+ * @param by
+ * @return first qualifying pass, or null
+ */
+ public T get(ModuleLocator by) {
+ return launcher.get(by);
+ }
+
+ /**
+ * Registers the object under the name in the settings map.
+ *
+ * Registered objects can be referenced by passes by name. Any existing
+ * object already registered under the name will be replaced.
+ *
+ * @param
+ * @param name
+ * @param object
+ * @return given object
+ */
+ public T setSetting(String name, T object) {
+ settings.put(name, object);
+ return object;
+ }
+ /**
+ * Registers the object under the name in the settings map, and creates
+ * a {@link GraphSetting} with the same name.
+ *
+ * @param
+ * @param name
+ * @param object
+ * @param defaultValue
+ * @param create true to create a GraphSetting, otherwise one will not be created and null returned
+ * @return created graph setting
+ * @see #setSetting(java.lang.String, java.lang.Object)
+ */
+ public GraphSetting setSetting(String name, T object, T defaultValue) {
+ setSetting(name, object);
+ return new GraphSetting<>(name, defaultValue);
+ }
+ /**
+ * Sets an integer in the settings map based on a boolean value.
+ *
+ * If the boolean is true, 0 is written, otherwise -1 is written. Commonly
+ * used to enable/disable features by switching a Junction to 0 or -1.
+ *
+ * @param name
+ * @param enable
+ * @return
+ * @see #setSetting(java.lang.String, java.lang.Object)
+ */
+ public int enableFeature(String name, boolean enable) {
+ return setSetting(name, enable ? 0 : -1);
+ }
+ /**
+ * Gets the object registered under the name in the settings map,
+ * or null if none is registered.
+ *
+ * @param
+ * @param name
+ * @return registered object, or null
+ */
+ public T getSetting(String name) {
+ Object obj = settings.get(name);
+ if (obj != null) {
+ return (T)obj;
+ } else {
+ return null;
+ }
+ }
+ /**
+ * Removes the object registered under the name in the settings map.
+ *
+ * @param
+ * @param name
+ * @return removed object, or null
+ */
+ public T removeSetting(String name) {
+ Object obj = settings.remove(name);
+ if (obj != null) {
+ return (T)obj;
+ } else {
+ return null;
+ }
+ }
+ /**
+ * Gets the settings map.
+ *
+ * The returned map may be modified.
+ *
+ * @return
+ */
+ public HashMap getSettingsMap() {
+ return settings;
+ }
+
+ /**
+ * Sets the name of this FrameGraph.
+ *
+ * @param name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+ /**
+ * Sets the asset path corresponding to a documentation file for
+ * this FrameGraph.
+ *
+ * @param docs asset path, or null for no documentation
+ */
+ public void setDocumentationAsset(String docs) {
+ this.docAsset = docs;
+ }
+ /**
+ * Sets the OpenCL context used for compute shading.
+ *
+ * @param clContext
+ */
+ public void setCLContext(Context clContext) {
+ context.setCLContext(clContext);
+ }
+ /**
+ * Assigns this framegraph to the OpenCL command queue.
+ *
+ * Passes do not need to use the assigned command queue, but are encouraged to.
+ *
+ * @param clQueue
+ */
+ public void setCLQueue(CommandQueue clQueue) {
+ context.setCLQueue(clQueue);
+ }
+ /**
+ * Enables printing of information useful for debugging.
+ *
+ * default=false
+ *
+ * @param debugPrint
+ */
+ public void enableDebugPrint(boolean debugPrint) {
+ this.debugPrint = debugPrint;
+ }
+
+ /**
+ * Called automatically to notify the FrameGraph that a {@link RenderThread} has completed execution.
+ *
+ * @param thread
+ */
+ public void notifyThreadComplete(RenderThread thread) {
+ launcher.notifyThreadComplete(thread);
+ }
+ /**
+ * Called automatically when a rendering exception occurs.
+ *
+ * @param ex
+ */
+ public void interruptRendering() {
+ if (!interrupted) {
+ interrupted = true;
+ launcher.interrupt();
+ }
+ }
+
+ /**
+ * Gets the {@link AssetManager} assigned to this FrameGraph.
+ *
+ * @return
+ */
+ public AssetManager getAssetManager() {
+ return assetManager;
+ }
+ /**
+ * Gets the {@link ResourceList} that manages resources for this FrameGraph.
+ *
+ * @return
+ */
+ public ResourceList getResources() {
+ return resources;
+ }
+ /**
+ * Gets the rendering context.
+ *
+ * @return
+ */
+ public FGRenderContext getContext() {
+ return context;
+ }
+ /**
+ * Gets the RenderManager.
+ *
+ * @return
+ */
+ public RenderManager getRenderManager() {
+ return context.getRenderManager();
+ }
+ /**
+ * Gets the OpenCL context used for compute shading, or null if not set.
+ *
+ * @return
+ */
+ public Context getCLContext() {
+ return context.getCLContext();
+ }
+ /**
+ * Gets the name of this framegraph.
+ *
+ * @return
+ */
+ public String getName() {
+ return name;
+ }
+ /**
+ * Returns an asset path corresponding to a documentation file for
+ * this FrameGraph.
+ *
+ * @return asset path, or null if no documentation is available
+ */
+ public String getDocumantationAsset() {
+ return docAsset;
+ }
+ /**
+ * Returns true if this framegraph is running asynchronous {@link PassThread}s.
+ *
+ * @return
+ */
+ public boolean isAsync() {
+ return launcher.isAsync();
+ }
+ /**
+ *
+ * @return
+ */
+ public boolean isDebugPrintEnabled() {
+ return debugPrint;
+ }
+
+ /**
+ * Applies the {@link FrameGraphData} to this FrameGraph.
+ *
+ * @param data
+ * @return
+ */
+ public final FrameGraph applyData(FrameGraphData data) {
+ name = data.getName();
+ for (SavableObject obj : data.getSettings()) {
+ setSetting(obj.getName(), obj.getObject());
+ }
+ return applyData(data.getModules());
+ }
+ /**
+ * Applies the {@link ModuleGraphData} to this FrameGraph.
+ *
+ * @param data
+ * @return this instance
+ * @throws IllegalStateException if the tree root is already a member of a FrameGraph
+ */
+ public final FrameGraph applyData(ModuleGraphData data) {
+ launcher.cleanupModule();
+ launcher = data.getRootModule(ThreadLauncher.class);
+ if (launcher.isAssigned()) {
+ throw new IllegalStateException("Cannot apply tree root that is already a member of a FrameGraph.");
+ }
+ launcher.initializeModule(this);
+ return this;
+ }
+ /**
+ * Applies the {@link ModuleGraphData} to this FrameGraph.
+ *
+ * @param data
+ * @return this instance
+ * @throws ClassCastException if the object is not an instance of {@link ModuleGraphData}.
+ * @throws NullPointerException if the object is null
+ */
+ public FrameGraph applyData(Object data) {
+ if (data != null) {
+ if (data instanceof FrameGraphData) {
+ return applyData((FrameGraphData)data);
+ } else if (data instanceof ModuleGraphData) {
+ return applyData((ModuleGraphData)data);
+ } else {
+ throw new ClassCastException(data.getClass()+" cannot be accepted as usable data.");
+ }
+ } else {
+ throw new NullPointerException("Data cannot be null.");
+ }
+ }
+ /**
+ * Loads and applies {@link ModuleGraphData} from the key.
+ *
+ * @param key
+ * @return
+ */
+ public FrameGraph loadData(FrameGraphKey key) {
+ return applyData(assetManager.loadFrameGraph(key));
+ }
+ /**
+ * Loads and applies {@link ModuleGraphData} at the specified asset path.
+ *
+ * @param assetPath
+ * @return
+ */
+ public FrameGraph loadData(String assetPath) {
+ return applyData(assetManager.loadFrameGraph(assetPath));
+ }
+ /**
+ * Creates exportable snapshot of this FrameGraph as {@link ModuleGraphData}.
+ *
+ * @return
+ */
+ public ModuleGraphData createModuleData() {
+ return new ModuleGraphData(launcher);
+ }
+
+ /**
+ * Returns the next unique module id for this FrameGraph.
+ *
+ * @return
+ */
+ public final int getNextId() {
+ return nextModuleId++;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java
new file mode 100644
index 0000000000..df6dca5873
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2024 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.renderer.framegraph;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.renderer.framegraph.light.TiledRenderGrid;
+import com.jme3.renderer.framegraph.client.GraphSetting;
+import com.jme3.renderer.framegraph.passes.*;
+
+/**
+ * Utility class for constructing common {@link FrameGraph}s in code.
+ *
+ * @author codex
+ */
+public class FrameGraphFactory {
+
+ /**
+ * Constructs a standard forward FrameGraph, with no controllable settings.
+ *
+ * @param assetManager
+ * @return forward framegraph
+ */
+ public static FrameGraph forward(AssetManager assetManager) {
+
+ FrameGraph fg = new FrameGraph(assetManager);
+ fg.setName("Forward");
+
+ SceneEnqueuePass enqueue = fg.add(new SceneEnqueuePass(true, true));
+ QueueMergePass merge = fg.add(new QueueMergePass(5));
+ OutputGeometryPass out = fg.add(new OutputGeometryPass());
+
+ merge.makeInput(enqueue, "Opaque", "Queues[0]");
+ merge.makeInput(enqueue, "Sky", "Queues[1]");
+ merge.makeInput(enqueue, "Transparent", "Queues[2]");
+ merge.makeInput(enqueue, "Gui", "Queues[3]");
+ merge.makeInput(enqueue, "Translucent", "Queues[4]");
+
+ out.makeInput(merge, "Result", "Geometry");
+
+ return fg;
+
+ }
+
+ /**
+ * Constructs a deferred FrameGraph.
+ *
+ * @param assetManager
+ * @param tiled true to enable tiled lighting
+ * @return deferred framegraph
+ */
+ public static FrameGraph deferred(AssetManager assetManager, boolean tiled) {
+ return deferred(assetManager, tiled, false);
+ }
+
+ /**
+ * Constructs a deferred FrameGraph.
+ *
+ * @param assetManager
+ * @param tiled true to enable tiled lighting
+ * @param async true to enable multithreading optimizations
+ * @return deferred framegraph
+ */
+ public static FrameGraph deferred(AssetManager assetManager, boolean tiled, boolean async) {
+
+ FrameGraph fg = new FrameGraph(assetManager);
+ fg.setName(tiled ? "TiledDeferred" : "Deferred");
+
+ int asyncThread = async ? 1 : PassIndex.MAIN_THREAD;
+
+ SceneEnqueuePass enqueue = fg.add(new SceneEnqueuePass(true, true));
+ Attribute tileInfoAttr = fg.add(new Attribute());
+ Junction tileJunct1 = fg.add(new Junction(1, 1));
+ GBufferPass gbuf = fg.add(new GBufferPass());
+ LightImagePass lightImg = fg.add(new LightImagePass(), new PassIndex(asyncThread, -1));
+ Junction lightJunct = fg.add(new Junction(1, 6));
+ Junction tileJunct2 = fg.add(new Junction(1, 2));
+ DeferredPass deferred = fg.add(new DeferredPass());
+ OutputPass defOut = fg.add(new OutputPass(0f));
+ QueueMergePass merge = fg.add(new QueueMergePass(4), new PassIndex(asyncThread, -1));
+ OutputGeometryPass geometry = fg.add(new OutputGeometryPass());
+
+ gbuf.makeInput(enqueue, "Opaque", "Geometry");
+
+ GraphSetting tileInfo = new GraphSetting<>("TileInfo", new TiledRenderGrid());
+ tileInfoAttr.setName("TileInfo");
+ tileInfoAttr.setSource(tileInfo);
+
+ GraphSetting tileToggle = fg.setSetting("EnableLightTiles", tiled ? 0 : -1, -1);
+ tileJunct1.makeInput(tileInfoAttr, Attribute.OUTPUT, Junction.getInput(0));
+ tileJunct1.setIndexSource(tileToggle);
+
+ lightImg.makeInput(enqueue, "OpaqueLights", "Lights");
+ lightImg.makeInput(tileJunct1, Junction.getOutput(), "TileInfo");
+
+ GraphSetting lightPackMethod = fg.setSetting("EnableLightTextures", tiled ? 0 : -1, -1);
+ lightJunct.setName("LightPackMethod");
+ lightJunct.makeGroupInput(lightImg, "Textures", Junction.getInput(0), 0, 0, 3);
+ lightJunct.makeInput(lightImg, "NumLights", Junction.getInput(0, 3));
+ lightJunct.makeInput(lightImg, "Ambient", Junction.getInput(0, 4));
+ lightJunct.makeInput(lightImg, "Probes", Junction.getInput(0, 5));
+ lightJunct.setIndexSource(lightPackMethod);
+
+ tileJunct2.makeGroupInput(lightImg, "TileTextures", Junction.getInput(0));
+ tileJunct2.setIndexSource(tileToggle);
+
+ deferred.makeGroupInput(gbuf, "GBufferData", "GBufferData");
+ deferred.makeInput(enqueue, "OpaqueLights", "Lights");
+ deferred.makeGroupInput(lightJunct, Junction.getOutput(), "LightTextures", 0, 0, 3);
+ deferred.makeInput(lightJunct, Junction.getOutput(3), "NumLights");
+ deferred.makeInput(lightJunct, Junction.getOutput(4), "Ambient");
+ deferred.makeInput(lightJunct, Junction.getOutput(5), "Probes");
+ deferred.makeGroupInput(tileJunct2, Junction.getOutput(), "TileTextures");
+
+ defOut.makeInput(deferred, "Color", "Color");
+ defOut.makeInput(gbuf, "GBufferData[4]", "Depth");
+
+ merge.makeInput(enqueue, "Sky", "Queues[0]");
+ merge.makeInput(enqueue, "Transparent", "Queues[1]");
+ merge.makeInput(enqueue, "Gui", "Queues[2]");
+ merge.makeInput(enqueue, "Translucent", "Queues[3]");
+
+ geometry.makeInput(merge, "Result", "Geometry");
+
+ return fg;
+
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FullScreenQuad.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FullScreenQuad.java
new file mode 100644
index 0000000000..648ceac898
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FullScreenQuad.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2024 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.renderer.framegraph;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.LightList;
+import com.jme3.material.Material;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.BufferUtils;
+
+/**
+ * Renders materials and textures on a static fullscreen quad geometry.
+ *
+ * Compatible with Filter shaders.
+ *
+ * @author codex
+ */
+public class FullScreenQuad {
+
+ private final Geometry geometry;
+ private final Material transferMat;
+
+ /**
+ *
+ * @param assetManager
+ */
+ public FullScreenQuad(AssetManager assetManager) {
+ Mesh mesh = new Mesh();
+ mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(
+ 0, 0, 0,
+ 1, 0, 0,
+ 0, 1, 0,
+ 1, 1, 0
+ ));
+ mesh.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(
+ 0, 1, 2,
+ 1, 3, 2
+ ));
+ mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(
+ 1, 0,
+ 1, 1,
+ 0, 0,
+ 0, 1
+ ));
+ mesh.updateBound();
+ mesh.updateCounts();
+ mesh.setStatic();
+ geometry = new Geometry("Screen", mesh);
+ transferMat = new Material(assetManager, "Common/MatDefs/ShadingCommon/TextureTransfer.j3md");
+ }
+
+ /**
+ * Renders the material on the quad.
+ *
+ * @param rm
+ * @param material
+ */
+ public void render(RenderManager rm, Material material) {
+ geometry.setMaterial(material);
+ geometry.updateGeometricState();
+ rm.renderGeometry(geometry);
+ }
+ /**
+ * Renders the material with the light list on the quad.
+ *
+ * @param rm
+ * @param material
+ * @param lights
+ */
+ public void render(RenderManager rm, Material material, LightList lights) {
+ geometry.setMaterial(material);
+ geometry.updateGeometricState();
+ rm.renderGeometry(geometry, lights);
+ }
+ /**
+ * Renders the color and depth textures on the quad, where the depth texture
+ * informs the depth value.
+ *
+ * @param rm
+ * @param color color texture, or null
+ * @param depth depth texture, or null
+ */
+ public void render(RenderManager rm, Texture2D color, Texture2D depth) {
+ boolean writeDepth = depth != null;
+ if (color != null || writeDepth) {
+ transferMat.setTexture("ColorMap", color);
+ transferMat.setTexture("DepthMap", depth);
+ transferMat.getAdditionalRenderState().setDepthTest(writeDepth);
+ transferMat.getAdditionalRenderState().setDepthWrite(writeDepth);
+ render(rm, transferMat);
+ setAlphaDiscard(null);
+ }
+ }
+
+ /**
+ * Sets the alpha discard threshold for next texture render.
+ *
+ * Fragments with alpha values below or equal to the threshold will be discarded.
+ *
+ * @param alphaDiscard
+ */
+ public void setAlphaDiscard(Float alphaDiscard) {
+ if (alphaDiscard == null) {
+ transferMat.clearParam("AlphaDiscard");
+ } else {
+ transferMat.setFloat("AlphaDiscard", alphaDiscard);
+ }
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/GeometryQueue.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GeometryQueue.java
new file mode 100644
index 0000000000..a2b793d91c
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GeometryQueue.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2024 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.renderer.framegraph;
+
+import com.jme3.renderer.DepthRange;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.GeometryRenderHandler;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.queue.GeometryComparator;
+import com.jme3.renderer.queue.NullComparator;
+import com.jme3.scene.Geometry;
+import com.jme3.util.ListSort;
+import java.util.ArrayList;
+
+/**
+ * Queue of ordered geometries for rendering.
+ *
+ * Similar to {@link GeometryList}, but designed for use in FrameGraphs. Specifically,
+ * this can store other GeometryQueues internally, essentially making queues able
+ * to merge very quickly and still maintain geometry order.
+ *
+ * @author codex
+ */
+public class GeometryQueue {
+
+ private static final int DEFAULT_SIZE = 32;
+
+ private Geometry[] geometries;
+ private GeometryComparator comparator;
+ private Camera cam;
+ private final ListSort listSort;
+ private final ArrayList internalQueues = new ArrayList<>();
+ private final DepthRange depth = new DepthRange();
+ private boolean updateFlag = true;
+ private boolean perspective = true;
+ private int size;
+
+ /**
+ * Geometry queue with default settings and a {@link NullComparator}.
+ */
+ public GeometryQueue() {
+ this(new NullComparator());
+ }
+ /**
+ * Geometry queue with default settings and the given comparator.
+ *
+ * @param comparator
+ */
+ public GeometryQueue(GeometryComparator comparator) {
+ size = 0;
+ geometries = new Geometry[DEFAULT_SIZE];
+ this.comparator = comparator;
+ listSort = new ListSort();
+ }
+
+ /**
+ * Sorts this queue and all internal queues.
+ */
+ public void sort() {
+ if (updateFlag && size > 1) {
+ // sort the spatial list using the comparator
+ if (listSort.getLength() != size) {
+ listSort.allocateStack(size);
+ }
+ listSort.sort(geometries, comparator);
+ updateFlag = false;
+ }
+ for (GeometryQueue q : internalQueues) {
+ q.sort();
+ }
+ }
+ /**
+ * Renders this queue and all internal queues.
+ *
+ * @param renderManager
+ * @param handler
+ */
+ public void render(RenderManager renderManager, GeometryRenderHandler handler) {
+ GeometryRenderHandler h;
+ if (handler == null) {
+ h = GeometryRenderHandler.DEFAULT;
+ } else {
+ h = handler;
+ }
+ renderManager.getRenderer().setDepthRange(depth);
+ if (!perspective) {
+ renderManager.setCamera(cam, true);
+ }
+ for (Geometry g : geometries) {
+ if (g == null) continue;
+ h.renderGeometry(renderManager, g);
+ g.queueDistance = Float.NEGATIVE_INFINITY;
+ }
+ if (!perspective) {
+ renderManager.setCamera(cam, false);
+ }
+ renderManager.getRenderer().setDepthRange(DepthRange.IDENTITY);
+ for (GeometryQueue q : internalQueues) {
+ q.render(renderManager, handler);
+ }
+ }
+
+ /**
+ * Adds the geometry to the queue.
+ *
+ * @param g
+ */
+ public void add(Geometry g) {
+ if (size == geometries.length) {
+ Geometry[] temp = new Geometry[size * 2];
+ System.arraycopy(geometries, 0, temp, 0, size);
+ geometries = temp; // original list replaced by double-size list
+ }
+ geometries[size++] = g;
+ updateFlag = true;
+ }
+ /**
+ * Sets the element at the given index.
+ *
+ * @param index The index to set
+ * @param value The value
+ */
+ public void set(int index, Geometry value) {
+ geometries[index] = value;
+ updateFlag = true;
+ }
+ /**
+ * Adds the geometry queue.
+ *
+ * @param q
+ */
+ public void add(GeometryQueue q) {
+ internalQueues.add(q);
+ }
+ /**
+ * Adds the geometry queue at the index.
+ *
+ * @param q
+ * @param index
+ */
+ public void add(GeometryQueue q, int index) {
+ internalQueues.add(index, q);
+ }
+
+ /**
+ * Resets list size to 0.
+ *
+ * Clears internal queue list, but does not clear internal queues.
+ */
+ public void clear() {
+ for (int i = 0; i < size; i++) {
+ geometries[i] = null;
+ }
+ internalQueues.clear();
+ updateFlag = true;
+ size = 0;
+ }
+
+ /**
+ * Marks this list as requiring sorting.
+ */
+ public void setUpdateNeeded() {
+ updateFlag = true;
+ }
+ /**
+ * Sets the comparator used to sort geometries.
+ *
+ * @param comparator
+ */
+ public void setComparator(GeometryComparator comparator) {
+ if (this.comparator != comparator) {
+ this.comparator = comparator;
+ updateFlag = true;
+ }
+ }
+ /**
+ * Set the camera that will be set on the geometry comparators
+ * via {@link GeometryComparator#setCamera(com.jme3.renderer.Camera)}.
+ *
+ * @param cam Camera to use for sorting.
+ */
+ public void setCamera(Camera cam) {
+ if (this.cam != cam) {
+ this.cam = cam;
+ comparator.setCamera(this.cam);
+ updateFlag = true;
+ }
+ for (GeometryQueue q : internalQueues) {
+ q.setCamera(cam);
+ }
+ }
+ /**
+ * Sets the depth range geometries in this queue (not internal queues)
+ * are rendered at.
+ *
+ * @param depth
+ */
+ public void setDepth(DepthRange depth) {
+ this.depth.set(depth);
+ }
+ /**
+ * Sets this queue (not internal queues) to render in perspective mode
+ * (as opposed to parallel projection or orthogonal).
+ *
+ * @param perspective
+ */
+ public void setPerspective(boolean perspective) {
+ this.perspective = perspective;
+ }
+
+ /**
+ * Returns the GeometryComparator that this Geometry list uses
+ * for sorting.
+ *
+ * @return the pre-existing instance
+ */
+ public GeometryComparator getComparator() {
+ return comparator;
+ }
+ /**
+ * Returns the number of elements in this GeometryList.
+ *
+ * @return Number of elements in the list
+ */
+ public int size() {
+ return size;
+ }
+ /**
+ * Returns the element at the given index.
+ *
+ * @param index The index to lookup
+ * @return Geometry at the index
+ */
+ public Geometry get(int index) {
+ return geometries[index];
+ }
+ /**
+ *
+ * @return
+ */
+ public DepthRange getDepth() {
+ return depth;
+ }
+ /**
+ *
+ * @return
+ */
+ public boolean isPerspective() {
+ return perspective;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/GraphRenderException.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GraphRenderException.java
new file mode 100644
index 0000000000..633a0f0cc3
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GraphRenderException.java
@@ -0,0 +1,18 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.renderer.framegraph;
+
+/**
+ * Raised when a fatal error occurs during FrameGraph processes.
+ *
+ * @author codex
+ */
+public class GraphRenderException extends RuntimeException {
+
+ public GraphRenderException(String message) {
+ super(message);
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java
new file mode 100644
index 0000000000..290a32d8ed
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2024 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.renderer.framegraph;
+
+/**
+ * Holds indices pointing to a render pass held within a framegraph.
+ *
+ * The FrameGraph system uses indices to schedule resources, particularly
+ * with reservations.
+ *
+ * Negative indices denote using defaults assigned by the FrameGraph.
+ *
+ * @author codex
+ */
+public final class PassIndex {
+
+ /**
+ * Index of the main render thread.
+ */
+ public static final int MAIN_THREAD = 0;
+
+ /**
+ * Index that conforms to defaults only.
+ */
+ public static final PassIndex DEFAULT = new PassIndex();
+
+ /**
+ * Index of the thread the pass is executed on.
+ */
+ public int threadIndex;
+ /**
+ * Index in the thread the pass is executed at.
+ */
+ public int queueIndex;
+
+ /**
+ * Creates a pass index with all defaults (negative indices).
+ */
+ public PassIndex() {
+ this(MAIN_THREAD, -1);
+ }
+ /**
+ *
+ * @param queueIndex
+ */
+ public PassIndex(int queueIndex) {
+ this(MAIN_THREAD, queueIndex);
+ }
+ /**
+ *
+ * @param threadIndex
+ * @param queueIndex
+ */
+ public PassIndex(int threadIndex, int queueIndex) {
+ this.threadIndex = threadIndex;
+ this.queueIndex = queueIndex;
+ }
+ /**
+ *
+ * @param index
+ */
+ public PassIndex(PassIndex index) {
+ this(index.threadIndex, index.queueIndex);
+ }
+
+ /**
+ *
+ * @param index index to set to (not null)
+ * @return this
+ */
+ public PassIndex set(PassIndex index) {
+ threadIndex = index.threadIndex;
+ queueIndex = index.queueIndex;
+ return this;
+ }
+ /**
+ *
+ * @param threadIndex
+ * @param queueIndex
+ * @return this
+ */
+ public PassIndex set(int threadIndex, int queueIndex) {
+ this.threadIndex = threadIndex;
+ this.queueIndex = queueIndex;
+ return this;
+ }
+ /**
+ *
+ * @param threadIndex
+ * @return this
+ */
+ public PassIndex setThreadIndex(int threadIndex) {
+ this.threadIndex = threadIndex;
+ return this;
+ }
+ /**
+ *
+ * @param queueIndex
+ * @return this
+ */
+ public PassIndex setQueueIndex(int queueIndex) {
+ this.queueIndex = queueIndex;
+ return this;
+ }
+
+ /**
+ * Shifts the thread index up or down one if the thread index is greater
+ * than the given index.
+ *
+ * @param i
+ * @param pos
+ * @return new thread index
+ */
+ public int shiftThread(int i, boolean pos) {
+ if (threadIndex > i) {
+ threadIndex += pos ? 1 : -1;
+ }
+ return threadIndex;
+ }
+ /**
+ * Shifts the queue index up or down one if the thread index is greater than
+ * the given index.
+ *
+ * @param i
+ * @param pos
+ * @return new queue index
+ */
+ public int shiftQueue(int i, boolean pos) {
+ if (queueIndex > i) {
+ queueIndex += pos ? 1 : -1;
+ }
+ return queueIndex;
+ }
+
+ /**
+ * Gets the index of the thread the pass is executed on.
+ *
+ * @return
+ */
+ public int getThreadIndex() {
+ return threadIndex;
+ }
+ /**
+ * Gets the index of the pass in a queue.
+ *
+ * @return
+ */
+ public int getQueueIndex() {
+ return queueIndex;
+ }
+
+ /**
+ * Returns true if this thread index matches the main thread index
+ *
+ * @return
+ */
+ public boolean isMainThread() {
+ return threadIndex == MAIN_THREAD;
+ }
+ /**
+ * Returns true if the default queue index is used (queue index is negative).
+ *
+ * @return
+ */
+ public boolean useDefaultQueueIndex() {
+ return queueIndex < 0;
+ }
+
+ /**
+ * Throws an exception if either index is less than zero.
+ */
+ public void requirePositive() {
+ if (threadIndex < 0) {
+ throw new IndexOutOfBoundsException("Thread index cannot be negative in this context.");
+ }
+ if (queueIndex < 0) {
+ throw new IndexOutOfBoundsException("Queue index cannot be negative in this context.");
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 19 * hash + this.threadIndex;
+ hash = 19 * hash + this.queueIndex;
+ return hash;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PassIndex other = (PassIndex) obj;
+ if (this.threadIndex != other.threadIndex) {
+ return false;
+ }
+ return this.queueIndex == other.queueIndex;
+ }
+ @Override
+ public String toString() {
+ return PassIndex.class.getSimpleName()+"[thread="+threadIndex+", queue="+queueIndex+']';
+ }
+ @Override
+ public PassIndex clone() {
+ return new PassIndex(threadIndex, queueIndex);
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java
new file mode 100644
index 0000000000..a1b5ec8e8b
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2024 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.renderer.framegraph;
+
+import com.jme3.renderer.framegraph.definitions.ResourceDef;
+import com.jme3.util.NativeObject;
+import java.util.LinkedList;
+import java.util.function.Consumer;
+
+/**
+ * Handles a raw object used for rendering processes within a FrameGraph.
+ *
+ * @author codex
+ * @param
+ */
+public class RenderObject {
+
+ private static final Consumer