FrameGraph API

Note: this is true and a good idea. As general advice to any code, just be careful of avoiding the trap of leaving the API “hard to use” because “an editor will fix it”. This often leads to code that is hard to use only because it tailored itself to an editor and/or left things unfinished because “that will be easier in a GUI”.

In my experience, there are lots of ways to make an API easier to use and they won’t break an eventual editor… and it’s nearly always great for the overall design in the end.

I’ll wait to offer specific advice until I have a chance to look at some examples/tutorials.

4 Likes

Hi @codex, you are awesome! You have done some great work.

I tried to pull your branch (FrameGraph) locally but I had some problems compiling it.
Sorry, I am not familiar with the whole JME project and FrameGraph at the moment.
Here are some errors I got when compiling locally:

JAVA_HOME="E:\Program Files\Java\jdk-11.0.10"
cd E:\workspace-se2\jmonkeyengine\jmonkeyengine; .\gradlew.bat --configure-on-demand -x check clean build
Configuration on demand is an incubating feature.

> Configure project :
Latest tag:null
Auto-detecting version
Revision: 8029
Hash: d2c56396076a9b851f013c49773069c6473a2328
Short Hash: d2c5639
Tag: null
Build Date: 2024-06-23
Build Branch: framegraphAPI
Use commit hash as version false
Base Version: null
Build Suffix: SNAPSHOT
Build Version: null-SNAPSHOT
Use natives snapshot: https://objects.jmonkeyengine.org/native-snapshots/3d49531fb98c8e41bf39d6a2167d5c3b95557148/jme3-natives.zip

> Task :cleanMergedJavadoc
> Task :clean
> Task :jme3-android:clean
> Task :jme3-android-native:clean
> Task :jme3-awt-dialogs:clean
> Task :jme3-core:clean
> Task :jme3-desktop:clean
> Task :jme3-effects:clean
> Task :jme3-examples:clean
> Task :jme3-ios:clean
> Task :jme3-jbullet:clean
> Task :jme3-jogg:clean
> Task :jme3-lwjgl:clean
> Task :jme3-lwjgl3:clean
> Task :jme3-networking:clean
> Task :jme3-niftygui:clean
> Task :jme3-plugins:clean
> Task :jme3-plugins-json:clean
> Task :jme3-plugins-json-gson:clean
> Task :jme3-terrain:clean
> Task :jme3-testdata:clean
> Task :jme3-vr:clean

> Task :getNativesZipFile
Download natives from https://objects.jmonkeyengine.org/native-snapshots/3d49531fb98c8e41bf39d6a2167d5c3b95557148/jme3-natives.zip to E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip

> Task :extractPrebuiltNatives
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\allocator\arm64-v8a\libbufferallocatorjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\allocator\arm64-v8a\libbufferallocatorjme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\allocator\armeabi-v7a\libbufferallocatorjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\allocator\armeabi-v7a\libbufferallocatorjme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\allocator\x86\libbufferallocatorjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\allocator\x86\libbufferallocatorjme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\allocator\x86_64\libbufferallocatorjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\allocator\x86_64\libbufferallocatorjme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\decode\arm64-v8a\libdecodejme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\decode\arm64-v8a\libdecodejme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\decode\armeabi-v7a\libdecodejme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\decode\armeabi-v7a\libdecodejme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\decode\x86\libdecodejme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\decode\x86\libdecodejme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\decode\x86_64\libdecodejme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\decode\x86_64\libdecodejme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\openalsoft\arm64-v8a\libopenalsoftjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\openalsoft\arm64-v8a\libopenalsoftjme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\openalsoft\armeabi-v7a\libopenalsoftjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\openalsoft\armeabi-v7a\libopenalsoftjme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\openalsoft\x86\libopenalsoftjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\openalsoft\x86\libopenalsoftjme.so
Copy E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\tmp\expandedArchives\3d49531fb98c8e41bf39d6a2167d5c3b95557148-natives.zip_440c706d9a68dc524b82177a42174207\android\openalsoft\x86_64\libopenalsoftjme.so E:\workspace-se2\jmonkeyengine\jmonkeyengine\build\native\android\openalsoft\x86_64\libopenalsoftjme.so

> Task :assemble
> Task :build

> Task :jme3-core:compileJava
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\export\InputCapsule.java:120: warning: [unchecked] unchecked cast
            return (T)s;
                      ^
  required: T
  found:    Savable
  where T is a type-variable:
    T extends Savable declared in method <T>readSavable(String,Class<T>,T)
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\export\InputCapsule.java:132: warning: [unchecked] unchecked call to add(E) as a member of the raw type Collection
            target.add(obj);
                      ^
  where E is a type-variable:
    E extends Object declared in interface Collection
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\util\NativeObject.java:250: warning: [unchecked] unchecked cast
        return (WeakReference<T>) weakRef;
                                  ^
  required: WeakReference<T>
  found:    WeakReference<NativeObject>
  where T is a type-variable:
    T extends Object declared in method <T>getWeakRef()
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\util\IntMap.java:266: warning: [unchecked] next() in IntMap.IntMapIterator implements next() in Iterator
        public Entry next() {
                     ^
  return type requires unchecked conversion from Entry to Entry<T>
  where E,T are type-variables:
    E extends Object declared in interface Iterator
    T extends Object declared in class IntMap
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\material\TechniqueDef.java:265: warning: [unchecked] unchecked cast
        return (T)logic;
                  ^
  required: T
  found:    TechniqueDefLogic
  where T is a type-variable:
    T extends TechniqueDefLogic declared in method <T>getLogic(Class<T>)
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\material\TechniqueDef.java:282: warning: [unchecked] unchecked cast
            return (T)logic;
                      ^
  required: T
  found:    TechniqueDefLogic
  where T is a type-variable:
    T extends TechniqueDefLogic declared in method <T>setLogicOrElse(Class<T>,Function<TechniqueDef,T>)
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\renderer\framegraph\FrameGraphData.java:72: error: incompatible types: Array is not a functional interface
        this.passes = passes.toArray(RenderPass[]::new);
                                     ^
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\renderer\framegraph\FrameGraphData.java:106: error: incompatible types: Array is not a functional interface
        out.write(list.toArray(SavablePassConnection[]::new), "connections", DEF_CONNECTIONS);
                               ^
  required: ResourceTicket<T>,T
  found: ResourceTicket,<null>
  where T is a type-variable:
    T extends Object declared in method <T>acquireOrElse(ResourceTicket<T>,T)
E:\workspace-se2\jmonkeyengine\jmonkeyengine\jme3-core\src\main\java\com\jme3\renderer\framegraph\passes\GroupAttribute.java:56: warning: [unchecked] unchecked conversion
            Object value = resources.acquireOrElse(inTickets[i], null);
                                                            ^
  required: ResourceTicket<T>
  found:    ResourceTicket
  where T is a type-variable:
    T extends Object declared in method <T>acquireOrElse(ResourceTicket<T>,T)
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files additionally use unchecked or unsafe operations.
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
2 errors
100 warnings

> Task :jme3-core:compileJava FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':jme3-core:compileJava'.
> Compilation failed; see the compiler error output for details.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

See https://docs.gradle.org/7.6.4/userguide/command_line_interface.html#sec:command_line_warnings

BUILD FAILED in 1m 39s
25 actionable tasks: 25 executed


The stack trace output in the console is a bit long, so I deleted some warnings.

2 Likes

Thanks for giving it a try! I’ve fixed some issues, and the branch now builds successfully on GitHub. Would you mind trying it again?

2 Likes

Thanks for your quick reply and handling.
I can compile now despite some warnings.

But I encountered some other problems, which may be related to my running environment. I think I need to find some time to learn it.

I get following exceptions when running the framegraph examples with assertions disabled.

java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 0
	at com.jme3.shader.DefineList.set(DefineList.java:80)
	at com.jme3.shader.DefineList.set(DefineList.java:89)
	at com.jme3.renderer.framegraph.passes.DeferredPass.makeCurrent(DeferredPass.java:190)
	at com.jme3.material.Technique.makeCurrent(Technique.java:153)
	at com.jme3.material.Material.render(Material.java:1075)
	at com.jme3.renderer.RenderManager.renderGeometry(RenderManager.java:812)
	at com.jme3.renderer.RenderManager.renderGeometry(RenderManager.java:739)
	at com.jme3.renderer.framegraph.FullScreenQuad.render(FullScreenQuad.java:92)
	at com.jme3.renderer.framegraph.FGRenderContext.renderFullscreen(FGRenderContext.java:182)
	at com.jme3.renderer.framegraph.passes.DeferredPass.execute(DeferredPass.java:161)
	at com.jme3.renderer.framegraph.passes.RenderPass.executeRender(RenderPass.java:110)
	at com.jme3.renderer.framegraph.FrameGraph.execute(FrameGraph.java:209)
	at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:1331)
	at com.jme3.renderer.RenderManager.render(RenderManager.java:1444)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:283)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:163)
	at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:225)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:245)
	at java.base/java.lang.Thread.run(Thread.java:1589)

If i enable assertions i can only run the examples once. Then i get following assertions errors:

java.lang.AssertionError
	at com.jme3.shader.DefineList.rangeCheck(DefineList.java:63)
	at com.jme3.shader.DefineList.set(DefineList.java:78)
	at com.jme3.renderer.framegraph.passes.DeferredPass.makeCurrent(DeferredPass.java:185)
	at com.jme3.material.Technique.makeCurrent(Technique.java:153)
	at com.jme3.material.Material.render(Material.java:1075)
	at com.jme3.renderer.RenderManager.renderGeometry(RenderManager.java:812)
	at com.jme3.renderer.framegraph.FullScreenQuad.render(FullScreenQuad.java:104)
	at com.jme3.renderer.framegraph.passes.DeferredPass.execute(DeferredPass.java:152)
	at com.jme3.renderer.framegraph.passes.RenderPass.executeRender(RenderPass.java:110)
	at com.jme3.renderer.framegraph.FrameGraph.execute(FrameGraph.java:209)
	at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:1331)
	at com.jme3.renderer.RenderManager.render(RenderManager.java:1444)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:283)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:163)
	at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:225)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:245)
	at java.base/java.lang.Thread.run(Thread.java:1589)

Also the output of the tilebased deferred is wrong:
The other tests i was not able to start.

I hope to have some time in the evening to continue testing.

3 Likes

Thanks for doing some testing @zzuegg. Much appreciated! :smiley: :+1:

Which commit were you using when you got the error? I remember encountering and fixing this error on commit 0d659aa. It was due to using a TechniqueDef that wasn’t initialized as expected by DeferredPass.

Unfortunately, the tiling is broken at the moment. Sometimes you can get tiling settings that it will work on, usually large tiles. For the deferred framegraph, you can change the tiling settings like this:

frameGraph.setSetting("TileInfo", new TiledRenderGrid(7, -1));

I’ve mostly been using jme3test.renderpath.TestSimpleDeferredLighting for tests. I’ll try to go through and fix all the tests today.

I am still trying to grasp the overall workings.

One thing i noticed, that i can start the test only once. At the second start i get an assertion error when the defines are set in the DeferredPass.makeCurrent.

In the first pass the supplied DefineList contains 5 entries, while at the second start it contains 0.

So there is eighter some static stuff that might not get cleared, or some caching going on

2 Likes

Hmm, that’s weird. I’m not seeing that issue no matter how many times I run it.
I added a patch to the latest commit that should fix it though.

Hello everyone, it’s been a while since I’ve posted an update, but here it is finally, after just over a month. (wow, where did all that time go??? :woozy_face:)

There are three important updates to the FrameGraph system this time:

  1. Implemented a new type of pass queue that is similar to the scene graph. RenderContainers (nodes) can contain multiple other RenderPasses and RenderContainers. This will be important for being able to save, load, and share small chunks of FrameGraphs independently.

  2. Added a RenderPipeline interface, which allows anyone to swap out the FrameGraph for their own rendering pipeline with no changes to the source code.

  3. Created a set of :point_right: wiki tutorials :point_left: on the basics of using FrameGraphs. There are still many topics not covered, but hopefully they are more helpful than confusing. :wink:

By now, this feature’s PR is simply humongous (181 files changed with an estimated 10,000 lines of code added). I realize this is a lot to ask, but it would be incredibly helpful to have some people review this PR, or at least small sections of it. Most of the code has been documented, and I would be more than happy to answer any questions (even dumb questions) or clarify any confusing documentation.

7 Likes

Last night I realized that with change #2 above, the FrameGraph system itself could be moved to an external library, where it couldn’t before. So I went ahead and moved all the relevant code to a new repository. Having the code in a separate library will remove most of the review/maintenance burden from JMonkeyEngine.

(It still uses ant, sorry about that. I will try to convert it to gradle later today.)

I’ve already closed the PR. The repository still depends on some changes in that PR, so I will make a new pull request for that later today as well.

8 Likes

Took much longer than anticipated, but I’ve gotten things rolling now.

The PR is a bit messy, but reviewing should be manageable. There are a lot of little insignificant changes everywhere, and a few leftover improvements that aren’t directly related to the core changes. Some of them are pretty useful.

For the external project(s), unfortunately, I was unable to get gradle to work with my network proxy, so it’ll have to be ant for now.

8 Likes

Reviewers Requested

I would greatly appreciate it if some people would volunteer to review the PR so it can move towards getting merged. Specifically, I was hoping someone experienced with rendering stuff would take a look (@RiccardoBlb @zzuegg ?). The core changes are relatively simple, and everything is at least basically documented.

Here is an example of basic usage. Additionally, you can take a look at DefaultPipelineContext and ForwardPipeline for exact implementations.

MyRenderPipeline:

public class MyRenderPipeline
        implements RenderPipeline<MyPipelineContext> {
    
    // tracks if this pipeline has rendered this frame
    private boolean rendered = false;
    
    public T fetchPipelineContext(RenderManager rm) {
        // get an existing context, or create a new context
        return rm.getOrCreateContext(MyPipelineContext.class,
                () -> new MyPipelineContext());
        
        // if you want to use the default context, use
        //return rm.getDefaultContext();
    }
    
    public boolean hasRenderedThisFrame() {
        return rendered;
    }
    
    public void startRenderFrame(RenderManager rm) {
        // Called when this pipeline starts rendering
        // for the first time this frame.
    }
    
    public void pipelineRender(RenderManager rm,
            T context, ViewPort vp, float tpf) {

        // perform actual rendering tasks

        // The "context" argument is the same as
        // returned from "fetchPipelineContext".

        rendered = true;
    }
    
    public void endRenderFrame(RenderManager rm) {
        // Called when all rendering is complete
        // in a render frame this context
        // participated in.
        rendered = false;
    }
    
}

MyPipelineContext:

public class MyPipelineContext implements PipelineContext {
    
    // put pipeline globals here
    
    // Tracks if this context has participated
    // in rendering this frame.
    private boolean rendered = false;
    
    @Override
    public boolean startViewPortRender(RenderManager rm, ViewPort vp) {
        // Called when viewport rendering starts and 
        // this context is participating.
        
        // Return false if this context has not
        // rendered yet this frame.
        return rendered;
    }

    @Override
    public void endViewPortRender(RenderManager rm, ViewPort vp) {
        // Called when viewport rendering ends and
        // this context is participating.
        rendered = true;
    }

    @Override
    public void endContextRenderFrame(RenderManager rm) {
        // Called when all rendering is complete
        // on a frame in which this context participated
        // in rendering something.
        rendered = false;
    }
    
}

Setting Pipelines:

// set pipeline to render this viewport
viewPort.setPipeline(new MyRenderPipeline());

// set default pipeline used when a viewport
// has no pipeline set
renderManager.setPipeline(new MyRenderPipeline());

I want the API to be easy to use, so if anything turns out to be confusing, please let me know.

Also, there are quite a few miscellaneous changes because the branch used to be much larger. If anything seems completely out of place, I can try to move it out of the PR (though for some things, that would be very difficult).

7 Likes

To help us fully understand the benefits of your solution, could you kindly provide the forum with visual aids, with test scenes and quantifiable data. This will complete the code review and ensure a full understanding of the functionality. Given the significant impact of this change, please consider creating visual representations of the overall API design, such as diagrams or a specification document, following John’s example. Thank you for your collaboration.

1 Like

For me the harvesting season started, so unless there comes a rain day i have super very limited time until october. i cannot promise anything that takes quite a few hours of continuous time right now. But i will look into it piece by piece

1 Like

Sure, it’s pretty simple. The new system inserts a customizable layer between the render manager and the renderer.

The RenderPipeline is now responsible for rendering a viewport’s attached scenes, instead of the RenderManager. The PipelineContext is responsible for managing “global” objects for the RenderPipeline, which solves the problem of needing to modify the RenderManager to store those global objects.

Note multiple RenderPipelines can run in the same frame. So, for example, the main viewport can be rendered using a deferred or forward++ pipeline, and the gui viewport can use something simpler like the default forward pipeline.

As for quantifiable data, the branch performs identically to the current master, so there isn’t much to say there.

8 Likes

I’ve settled on the name “Renthyl” for the divergent FrameGraph project. I’ve also decided split the project into two repositories: Renthyl and RenthylPlus. The former contains only the core FrameGraph features (required), and RenthylPlus contains optional extensions

With that, I made an alpha release for Renthyl today. It still depends on the separate JME branch, which, frankly, hasn’t made much progress towards getting merged so far. RenthylPlus is still a ways from getting released; there is still a few bugs with tiled deferred, and I had really hoped to add forward++ support.

8 Likes

Regarding the review, i have started. At a first lookover i am not a fan of the geometryhandler and how it is handled, but i am not yet deep enough to have a strong opinion about it. It might be too limited for a general usecase. Maybe it would be better to just pass the whole queue to the handler i case of a specialized pipeline…

I am writing this here because github on mobile is kind of strange

4 Likes

I noticed in renthyl plus you provide your own materials, does it somehow replace the jme provided materials?

1 Like

Kind of. Those material definitions are merged into the JME provided material definitions at runtime in GBufferPass using MaterialAdapter, which I added as part of my utility library. That may sound sketchy, but it’s actually been working very well and provides much more flexibility than if JME’s materials had been replaced outright.

1 Like

Beside the mixing of deferred and forward shaded objects what are the other usecases of the geometry handler? Just asking so i do not habe to search the e tire codebase