diff --git a/build.gradle b/build.gradle index bb86d0101a..cfcfc5c90c 100644 --- a/build.gradle +++ b/build.gradle @@ -286,4 +286,4 @@ retrolambda { javaVersion JavaVersion.VERSION_1_7 incremental true jvmArgs '-noverify' -} \ No newline at end of file +} 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..6e40ff366e 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -824,4 +824,4 @@ public TechniqueDef clone() throws CloneNotSupportedException { return clone; } -} +} \ No newline at end of file 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..c59ffd9084 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 @@ -31,6 +31,10 @@ */ package com.jme3.renderer; +import com.jme3.renderer.pipeline.ForwardPipeline; +import com.jme3.renderer.pipeline.DefaultPipelineContext; +import com.jme3.renderer.pipeline.RenderPipeline; +import com.jme3.renderer.pipeline.PipelineContext; import com.jme3.light.DefaultLightFilter; import com.jme3.light.LightFilter; import com.jme3.light.LightList; @@ -44,7 +48,6 @@ import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; import com.jme3.profile.AppStep; -import com.jme3.profile.SpStep; import com.jme3.profile.VpStep; import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; @@ -65,8 +68,12 @@ import com.jme3.util.SafeArrayList; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.logging.Logger; /** @@ -87,6 +94,10 @@ public class RenderManager { 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 final LinkedList usedPipelines = new LinkedList<>(); + private RenderPipeline defaultPipeline = new ForwardPipeline(); private Camera prevCam = null; private Material forcedMaterial = null; private String forcedTechnique = null; @@ -104,7 +115,7 @@ 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; @@ -117,6 +128,115 @@ public class RenderManager { public RenderManager(Renderer renderer) { this.renderer = renderer; this.forcedOverrides.add(boundDrawBufferId); + // register default pipeline context + 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) { + assert pipeline != null; + 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 registered context or null + */ + 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 from the supplier. + * + * @param + * @param type + * @param supplier interface for creating a new context if necessary + * @return registered or newly created context + */ + public T getOrCreateContext(Class type, Supplier supplier) { + T c = getContext(type); + if (c == null) { + c = supplier.get(); + registerContext(type, c); + } + return c; + } + + /** + * Gets the pipeline context registered under the class or creates + * and registers a new context from the function. + * + * @param + * @param type + * @param function interface for creating a new context if necessary + * @return registered or newly created context + */ + public T getOrCreateContext(Class type, Function function) { + T c = getContext(type); + if (c == null) { + c = function.apply(this); + registerContext(type, c); + } + return c; + } + + /** + * Registers the pipeline context under the class. + *

+ * If another context is already registered under the class, that + * context will be replaced by the given context. + * + * @param + * @param type class type to register the context under (not null) + * @param context context to register (not null) + */ + public void registerContext(Class type, T context) { + assert type != null; + if (context == null) { + throw new NullPointerException("Context to register cannot be null."); + } + contexts.put(type, context); + } + + /** + * Gets the application profiler. + * + * @return + */ + public AppProfiler getProfiler() { + return prof; } /** @@ -402,7 +522,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 +542,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 @@ -628,7 +757,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 +791,15 @@ 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; - } - 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 +883,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 +902,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,10 +933,10 @@ 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 + // queue the scene for rendering renderSubScene(scene, vp); } @@ -800,12 +947,10 @@ public void renderScene(Spatial scene, ViewPort vp) { * @param vp the ViewPort to render in (not null) */ private void renderSubScene(Spatial scene, ViewPort vp) { - - // check culling first. + // check culling first if (!scene.checkCulling(vp.getCamera())) { return; } - scene.runControlRender(this, vp); if (scene instanceof Node) { // Recurse for all children @@ -819,12 +964,11 @@ private void renderSubScene(Spatial scene, ViewPort vp) { renderSubScene(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 +1182,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()); @@ -1120,141 +1264,54 @@ public void renderViewPortRaw(ViewPort vp) { } /** - * Renders the {@link ViewPort}. - * - *

If the ViewPort is {@link ViewPort#isEnabled() disabled}, this method - * returns immediately. Otherwise, the ViewPort is rendered by - * the following process:
- *

    - *
  • All {@link SceneProcessor scene processors} that are attached - * to the ViewPort are {@link SceneProcessor#initialize(com.jme3.renderer.RenderManager, - * com.jme3.renderer.ViewPort) initialized}. - *
  • - *
  • The SceneProcessors' {@link SceneProcessor#preFrame(float) } method - * is called.
  • - *
  • The ViewPort's {@link ViewPort#getOutputFrameBuffer() output framebuffer} - * is set on the Renderer
  • - *
  • The camera is set on the renderer, including its view port parameters. - * (see {@link #setCamera(com.jme3.renderer.Camera, boolean) })
  • - *
  • Any buffers that the ViewPort requests to be cleared are cleared - * and the {@link ViewPort#getBackgroundColor() background color} is set
  • - *
  • Every scene that is attached to the ViewPort is flattened into - * the ViewPort's render queue - * (see {@link #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) }) - *
  • - *
  • The SceneProcessors' {@link SceneProcessor#postQueue(com.jme3.renderer.queue.RenderQueue) } - * method is called.
  • - *
  • The render queue is sorted and then flushed, sending - * rendering commands to the underlying Renderer implementation. - * (see {@link #flushQueue(com.jme3.renderer.ViewPort) })
  • - *
  • The SceneProcessors' {@link SceneProcessor#postFrame(com.jme3.texture.FrameBuffer) } - * method is called.
  • - *
  • The translucent queue of the ViewPort is sorted and then flushed - * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })
  • - *
  • If any objects remained in the render queue, they are removed - * from the queue. This is generally objects added to the - * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, - * com.jme3.renderer.RenderManager, com.jme3.renderer.Camera) - * shadow queue} - * which were not rendered because of a missing shadow renderer.
  • - *
- * - * @param vp View port to render - * @param tpf Time per frame value + * Applies the ViewPort's Camera and FrameBuffer in preparation + * for rendering. + * + * @param vp */ - public void renderViewPort(ViewPort vp, float tpf) { - if (!vp.isEnabled()) { - return; - } - 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); - } - } - + 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()); - } - - if (prof != null) { - prof.vpStep(VpStep.RenderScene, vp, null); + renderer.clearBuffers(vp.isClearColor(), vp.isClearDepth(), vp.isClearStencil()); } - 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()); - } + } + + /** + * Renders the {@link ViewPort} using the ViewPort's {@link RenderPipeline}. + *

+ * If the ViewPort's RenderPipeline is null, the pipeline returned by + * {@link #getPipeline()} is used instead. + *

+ * If the ViewPort is disabled, no rendering will occur. + * + * @param vp View port to render + * @param tpf Time per frame value + */ + public void renderViewPort(ViewPort vp, float tpf) { + if (!vp.isEnabled()) { + return; + } + RenderPipeline pipeline = vp.getPipeline(); + if (pipeline == null) { + pipeline = defaultPipeline; } - - if (prof != null) { - prof.vpStep(VpStep.FlushQueue, vp, null); + PipelineContext context = pipeline.fetchPipelineContext(this); + if (context == null) { + throw new NullPointerException("Failed to fetch pipeline context."); } - 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); - } + if (!context.startViewPortRender(this, vp)) { + usedContexts.add(context); } - //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); + if (!pipeline.hasRenderedThisFrame()) { + usedPipelines.add(pipeline); + pipeline.startRenderFrame(this); } + pipeline.pipelineRender(this, context, vp, tpf); + context.endViewPortRender(this, vp); } /** @@ -1276,7 +1333,7 @@ public void render(float tpf, boolean mainFrameBufferActive) { if (renderer instanceof NullRenderer) { return; } - + uniformBindingManager.newFrame(); if (prof != null) { @@ -1308,12 +1365,22 @@ public void render(float tpf, boolean mainFrameBufferActive) { renderViewPort(vp, tpf); } } + + // cleanup for used render pipelines and pipeline contexts only + for (PipelineContext c : usedContexts) { + c.endContextRenderFrame(this); + } + for (RenderPipeline p : usedPipelines) { + p.endRenderFrame(this); + } + usedContexts.clear(); + usedPipelines.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 +1391,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 +1404,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 +1418,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/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java index 6f0424fb30..f256405276 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 @@ -31,6 +31,7 @@ */ package com.jme3.renderer; +import com.jme3.renderer.pipeline.RenderPipeline; import com.jme3.math.ColorRGBA; import com.jme3.post.SceneProcessor; import com.jme3.renderer.queue.RenderQueue; @@ -84,6 +85,10 @@ public class ViewPort { * Scene processors currently applied. */ protected final SafeArrayList processors = new SafeArrayList<>(SceneProcessor.class); + /** + * Dedicated pipeline. + */ + protected RenderPipeline pipeline; /** * FrameBuffer for output. */ @@ -424,5 +429,28 @@ public void setEnabled(boolean enable) { public boolean isEnabled() { return enabled; } + + /** + * Sets the pipeline used by this viewport for rendering. + *

+ * If null, the render manager's default pipeline will be used + * to render this viewport. + *

+ * default=null + * + * @param pipeline pipeline, or null to use render manager's pipeline + */ + public void setPipeline(RenderPipeline pipeline) { + this.pipeline = pipeline; + } + + /** + * Gets the framegraph used by this viewport for rendering. + * + * @return + */ + public RenderPipeline getPipeline() { + return pipeline; + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java new file mode 100644 index 0000000000..d4af168a6d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java @@ -0,0 +1,62 @@ +/* + * 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.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Default implementation of AbstractPipelineContext that + * does nothing extra. + * + * @author codex + */ +public class DefaultPipelineContext implements PipelineContext { + + // decided to use an atomic boolean, since it is less hassle + private final AtomicBoolean rendered = new AtomicBoolean(false); + + @Override + public boolean startViewPortRender(RenderManager rm, ViewPort vp) { + return rendered.getAndSet(true); + } + + @Override + public void endViewPortRender(RenderManager rm, ViewPort vp) {} + + @Override + public void endContextRenderFrame(RenderManager rm) { + rendered.set(false); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java new file mode 100644 index 0000000000..2f02ab3e82 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java @@ -0,0 +1,159 @@ +/* + * 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.pipeline; + +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.SpStep; +import com.jme3.profile.VpStep; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.util.SafeArrayList; +import java.util.List; + +/** + * Port of the standard forward renderer to a pipeline. + * + * @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 startRenderFrame(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/pipeline/NullPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java new file mode 100644 index 0000000000..b104e5bd5d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java @@ -0,0 +1,42 @@ +/* + * 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.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Render pipeline that performs no rendering. + * + * @author codex + */ +public class NullPipeline implements RenderPipeline { + + private boolean rendered = false; + + @Override + public PipelineContext fetchPipelineContext(RenderManager rm) { + return rm.getDefaultContext(); + } + + @Override + public boolean hasRenderedThisFrame() { + return rendered; + } + + @Override + public void startRenderFrame(RenderManager rm) {} + + @Override + public void pipelineRender(RenderManager rm, PipelineContext context, ViewPort vp, float tpf) { + rendered = true; + } + + @Override + public void endRenderFrame(RenderManager rm) { + rendered = false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java new file mode 100644 index 0000000000..32a4fa2e2d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java @@ -0,0 +1,70 @@ +/* + * 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.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Handles objects globally for a single type of RenderPipeline. + * + * @author codex + */ +public interface PipelineContext { + + /** + * Called when a ViewPort rendering session starts that this context + * is participating in. + * + * @param rm + * @param vp viewport being rendered + * @return true if this context has already rendered a viewport this frame + */ + public boolean startViewPortRender(RenderManager rm, ViewPort vp); + + /** + * Called when viewport rendering session ends that this context + * is participating in. + * + * @param rm + * @param vp viewport being rendered + */ + public void endViewPortRender(RenderManager rm, ViewPort vp); + + /** + * Called at the end of a render frame this context participated in. + * + * @param rm + */ + public void endContextRenderFrame(RenderManager rm); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java new file mode 100644 index 0000000000..44f76d6e86 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java @@ -0,0 +1,88 @@ +/* + * 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.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Pipeline for rendering 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 startRenderFrame(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/queue/RenderQueue.java b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java index 86c5bc5280..a16dc18665 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java @@ -264,7 +264,7 @@ public void addToQueue(Geometry g, Bucket bucket) { } } - private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean flush) { list.setCamera(cam); // select camera for sorting list.sort(); for (int i = 0; i < list.size(); i++) { @@ -273,7 +273,7 @@ private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, rm.renderGeometry(obj); obj.queueDistance = Float.NEGATIVE_INFINITY; } - if (clear) { + if (flush) { list.clear(); } } @@ -329,6 +329,18 @@ public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean cle } rm.getRenderer().popDebugGroup(); } + + public GeometryList getList(Bucket bucket) { + switch (bucket) { + case Opaque: return opaqueList; + case Gui: return guiList; + case Transparent: return transparentList; + case Translucent: return translucentList; + case Sky: return skyList; + default: + throw new UnsupportedOperationException(); + } + } public void clear() { opaqueList.clear(); diff --git a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java index 4cefa6ab6f..2ec97f60f5 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -179,7 +179,6 @@ public int getLayer() { return this.layer; } } - public static class FrameBufferTextureTarget extends RenderBuffer { private FrameBufferTextureTarget(){} @@ -260,12 +259,44 @@ public void addColorTarget(FrameBufferBufferTarget colorBuf){ colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } - + public void addColorTarget(FrameBufferTextureTarget colorBuf){ // checkSetTexture(colorBuf.getTexture(), false); // TODO: this won't work for levels. colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } + + /** + * Replaces the color target at the index. + *

+ * A color target must already exist at the index, otherwise + * an exception will be thrown. + * + * @param i index of color target to replace + * @param colorBuf color target to replace with + */ + public void replaceColorTarget(int i, FrameBufferTextureTarget colorBuf) { + if (i < 0 || i >= colorBufs.size()) { + throw new IndexOutOfBoundsException("No color target exists to replace at index=" + i); + } + colorBuf.slot = i; + colorBufs.set(i, colorBuf); + } + + /** + * Removes the color target at the index. + *

+ * Color targets above the removed target will have their + * slot indices shifted accordingly. + * + * @param i + */ + public void removeColorTarget(int i) { + colorBufs.remove(i); + for (; i < colorBufs.size(); i++) { + colorBufs.get(i).slot = i; + } + } /** * Adds a texture to one of the color Buffers Array. It uses {@link TextureCubeMap} ordinal number for the diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md index 08a7d007f8..7586e854fb 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md @@ -61,4 +61,4 @@ MaterialDef Phong Lighting Deferred { Technique { } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert index 1d22a36edb..e247694a98 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert @@ -9,4 +9,4 @@ void main(){ texCoord = inTexCoord; vec4 pos = vec4(inPosition, 1.0); gl_Position = vec4(sign(pos.xy-vec2(0.5)), 0.0, 1.0); -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert index 66eca303b0..d72712854f 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert @@ -70,4 +70,4 @@ void main(){ #ifdef VERTEX_COLOR DiffuseSum *= inColor; #endif -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib index 37c3a40cf2..bffb200084 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -106,4 +106,4 @@ vec3 TransformWorldNormal(vec3 normal) { } -#endif \ No newline at end of file +#endif diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 0a0c0403c2..58921e9e9e 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -970,4 +970,4 @@ public void applyToTexture(final Texture texture) { textureOption.applyToTexture(value, texture); } } -} +} \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java index e8f5029c37..6174332699 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java @@ -243,4 +243,4 @@ public void read(JmeImporter im) throws IOException { focusRange = ic.readFloat("focusRange", 10f); debugUnfocus = ic.readBoolean("debugUnfocus", false); } -} +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag index 7ee2e51d4a..219459931b 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag @@ -55,4 +55,4 @@ void main(){ color = mix (color,m_EdgeColor.rgb,edgeAmount); gl_FragColor = vec4(color, 1.0); -} \ No newline at end of file +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag index fe52cfc502..99b31175e4 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag @@ -51,4 +51,4 @@ void main() { vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper); gl_FragColor = mix(paperColor, lineColor, linePixel); -} \ No newline at end of file +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag index eb03e8f565..edd29d88c5 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag @@ -86,4 +86,4 @@ void main() { vec4 texVal = texture2D(m_Texture, texCoord); gl_FragColor = vec4(FxaaPixelShader(posPos, m_Texture, g_ResolutionInverse), texVal.a); -} \ No newline at end of file +} diff --git a/jme3-examples/gradle.properties b/jme3-examples/gradle.properties index 5b85c3b85b..bfcfb949b5 100644 --- a/jme3-examples/gradle.properties +++ b/jme3-examples/gradle.properties @@ -2,4 +2,4 @@ assertions = true # Build javadoc per Github issue #1366 -buildJavaDoc = true \ No newline at end of file +buildJavaDoc = true diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md index d2bfa0c01d..f84da24224 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md @@ -42,4 +42,4 @@ MaterialDef Terrain { Technique { } -} \ No newline at end of file +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md index 1ac3b21dc1..00bb60445d 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md @@ -18,7 +18,7 @@ MaterialDef Terrain { Technique { VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.vert FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.frag - + WorldParameters { WorldViewProjectionMatrix } @@ -31,4 +31,4 @@ MaterialDef Terrain { Technique { } -} \ No newline at end of file +}