Parcourir la source

Render Pipeline Article (#180)

* Add render pipeline article
codex il y a 4 mois
Parent
commit
a917006bc5

+ 2 - 0
docs/modules/core/nav.adoc

@@ -59,6 +59,8 @@
 ** xref:renderer/remote-controlling_the_camera.adoc[Remote-Controlling]
 ** xref:renderer/multiple_camera_views.adoc[Multiple Camera Views]
 ** xref:renderer/jme3_renderbuckets.adoc[Render Buckets]
+* Rendering
+** xref:renderer/render_pipeline.adoc[Render Pipelines]
 * User Interaction
 ** xref:input/input_handling.adoc[Input Handling]
 ** xref:input/combo_moves.adoc[Combo Moves]

+ 168 - 0
docs/modules/core/pages/renderer/render_pipeline.adoc

@@ -0,0 +1,168 @@
+= Render Pipelines
+:revnumber: 1.0
+:revdate: 2025/04/22
+:keywords: rendering, viewport, pipeline
+
+Since JMonkeyEngine 3.8, ViewPorts are rendered using render pipelines. Before, the RenderManager was entirely responsible for processing each ViewPort's scenes and rendering their geometries. Only basic forward rendering could really be used without great difficulty, so render pipelines were introduced to allow developers to implement whatever rendering techniques their game demands.
+
+The sky is the limit with render pipelines, however, because they control every aspect of rendering a ViewPort (including SceneProcessors and profiling), a great deal goes into implementing a pipeline.
+
+== PipelineContext
+
+Before diving into building render pipelines, one must first have at least a rudimentary knowledge of PipelineContexts, which are responsible for handling global objects for pipelines. Pipelines themselves cannot manage these global objects because they are localized to specific ViewPorts. PipelineContexts are stored directly by the RenderManager, so they are better suited for this task.
+
+[source,java]
+----
+public class MyPipelineContext implements PipelineContext {
+
+    // an example of a global object to manage
+    private GlobalResources resources;
+
+    @Override
+    public boolean startViewPortRender(RenderManager rm, ViewPort vp) {
+        // Called when a ViewPort begins rendering that
+        // this context is involved in.
+        // Must return true if this context was already involved
+        // in a ViewPort rendering this frame.
+    }
+
+    @Override
+    public void endViewPortRender(RenderManager rm, ViewPort vp) {
+        // Called when a ViewPort ends rendering that
+        // this context was involved in.
+    }
+
+    @Override
+    public void endContextRenderFrame(RenderManager rm) {
+        // Called after all rendering this frame is complete AND this
+        // context was involved in rendering a ViewPort this frame.
+    }
+
+    // give pipelines access to the example global object
+    public GlobalResources getResources() {
+        return resources;
+    }
+
+}
+----
+
+Note that PipelineContexts get run only if they become involved in rendering a ViewPort. They do so when a pipeline specifically selects them for rendering, which will be covered later.
+
+In order to be selected at all, PipelineContexts must be registered with the RenderManager at the time. This can either be done manually, or the pipeline itself can create and register the context if it does not yet exist.
+
+[source,java,opts=novalidate]
+----
+// register context manually
+renderManager.registerContext(MyPipelineContext.class, new MyPipelineContext());
+----
+
+Contexts are registered by a class type by which they can then be retrieved.
+
+== RenderPipeline
+
+The RenderPipeline interface is the primary element of the pipeline system, as it is directly responsible for rendering a ViewPort. RenderPipeline provides five methods to implement:
+
+[source,java]
+----
+public class MyPipeline implements RenderPipeline<MyPipelineContext> {
+
+    @Override
+    public MyPipelineContext fetchPipelineContext(RenderManager rm) {
+        // Returns a PipelineContext from the RenderManager
+        // that handles global objects for this pipeline. The
+        // returned context is passed to pipelineRender.
+    }
+
+    @Override
+    public boolean hasRenderedThisFrame() {
+        // Returns true if this context has performed any
+        // rendering previously on this frame.
+    }
+
+    @Override
+    public void startRenderFrame() {
+        // Called before pipelineRender on the first rendering
+        // this pipeline is to perform this frame.
+    }
+
+    @Override
+    public void pipelineRender(RenderManager rm, MyPipelineContext context, ViewPort vp, float tpf) {
+        // Does the actual rendering of the ViewPort.
+    }
+
+    @Override
+    public void endRenderFrame(RenderManager rm) {
+        // Called after all rendering is complete in a frame in
+        // which this pipeline rendered a ViewPort.
+    }
+
+}
+----
+
+The `pipelineRender` method can get quite complicated, as rendering is a complicated process. Fortunately, the pipeline system imposes little to no restriction on what pipelines actually do during rendering. Here is a quick `renderPipeline` implementation to get started with:
+
+[source,java]
+----
+@Override
+public void pipelineRender(RenderManager rm, MyPipelineContext context, ViewPort vp, float tpf) {
+    // apply viewport to rendering context
+    rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
+    rm.getRenderer().clearBuffers(true, true, true);
+    rm.getRenderer().setBackgroundColor(vp.getBackgroundColor());
+    rm.setCamera(vp.getCamera(), false);
+    // render each geometry in all viewport scenes
+    for (Spatial scene : vp.getScenes()) {
+        scene.depthFirstTraversal(s -> {
+            if (s instanceof Geometry) {
+                rm.renderGeometry((Geometry)s);
+            }
+        });
+    }
+    // reset clip rect
+    rm.getRenderer().clearClipRect();
+}
+----
+
+As previously mentioned, there is very little restriction over what `pipelineRender` does, so following the above example is not required. In fact, JMonkeyEngine's default renderer, https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java[ForwardPipeline], looks vastly different.
+
+=== Fetching Contexts to Use
+
+Since pipelines often depend on global objects (as stated before), the RenderPipeline interface has a generic specifying the type of context to be expected (set as MyPipelineContext in the example above), and the interface provides the `fetchPipelineContext` method to select the context to use during rendering. The context returned by `fetchPipelineContext` will then be passed to `pipelineRender` to actually be used.
+
+For example, if the pipeline wanted to select MyPipelineContext that is already registered with the RenderManager:
+
+[source,java]
+----
+@Override
+public MyPipelineContext fetchPipelineContext(RenderManager rm) {
+    // assuming MyPipelineContext is registered under MyPipelineContext.class
+    return rm.getContext(MyPipelineContext.class);
+}
+----
+
+Even if a RenderPipeline does not need to use a PipelineContext, it is still required that `fetchPipelineContext` return a non-null context. For such cases, returning `rm.getDefaultContext()` is acceptable.
+
+== Usage
+
+In order to get a RenderPipeline to render a ViewPort, simply assign the pipeline to the ViewPort. When the rendering step occurs, the RenderManager uses each ViewPort's assigned pipeline to render the ViewPort.
+
+[source,java,opts=novalidate]
+----
+viewPort.setPipeline(new MyRenderPipeline());
+----
+
+Note that RenderPipelines (unless otherwise specified) can be assigned to multiple ViewPorts at once.
+
+[source,java,opts=novalidate]
+----
+MyRenderPipeline p = new MyRenderPipeline();
+viewPort.setPipeline(p);
+guiViewPort.setPipeline(p);
+----
+
+If no pipeline is assigned to a ViewPort, the RenderManager uses a default pipeline to render that ViewPort. The default pipeline can be set as so:
+
+[source,java,opts=novalidate]
+----
+renderManager.setPipeline(new MyRenderPipeline());
+----