How to customize nodes Movie Render Graph Config in C++?

Hi Unreal Team,

Currently I am testing out how to build the Movie Render Graph manually from C++. I managed to create it, but I faced some problems trying to set the custom resolution and adding the output formats.

I only found Named Resolution from Profile, but no clear settings to define Custom resolution. How would it be possible to do it?

The other thing is adding the output formats.

I included the header for the “MovieGraphImageSequenceOutputNode.h”

Added “MovieRenderPipelineRenderPasses” module to the Build.cs file

When I try to add the Node itself the compilers shows this error:

error LNK2019: unresolved external symbol “private: static class UClass * __cdecl UMovieGraphImageSequenceOutputNode_JPG::GetPrivateStaticClass(void)” (?GetPrivateStaticClass@UMovieGraphImageSequenceOutputNode_JPG@@CAPEAVUClass@@XZ) referenced in function "public: bool __cdecl UObjectBaseUtility::IsA<class UMovieGraphImageSequenceOutputNode_JPG>(void)const " (??$IsA@VUMovieGraphImageSequenceOutputNode_JPG@@@UObjectBaseUtility@@QEBA_NXZ)

It seems the private class is not exposed, however you CreateBasicConfig_Internal() function in MovieGraphCreateConfigExample.py uses the same method, without any problem.

So my question what is the preferred way to build these nodes up in c++ and customize these settings?

Thanks in advance!

Zoltán

`if (JPG)
{
UMovieGraphImageSequenceOutputNode_JPG* JPGNode = Cast<UMovieGraphImageSequenceOutputNode_JPG>(TmpRenderGraphConfig->CreateNodeByClass(UMovieGraphImageSequenceOutputNode_JPG::StaticClass()));

// Connect Output Setting to JPG
TmpRenderGraphConfig->AddLabeledEdge(OutputSettingNode, “”, JPGNode, “”);

// Connect JPG To Output Node
UMovieGraphNode* OutputNode = TmpRenderGraphConfig->GetOutputNode();
TmpRenderGraphConfig->AddLabeledEdge(JPGNode, “”, OutputNode, “Globals”);
}`

Steps to Reproduce

  1. Create function for adding nodes
  2. Add MovieRenderPipelineRenderPassesto the Build.cs file
  3. Include MovieGraphImageSequenceOutputNode.hin the cpp
  4. Call this function
  5. Compile

UMovieGraphImageSequenceOutputNode_JPG* JPGNode = Cast<UMovieGraphImageSequenceOutputNode_JPG>(TmpRenderGraphConfig->CreateNodeByClass(UMovieGraphImageSequenceOutputNode_JPG::StaticClass()));

This unfortunately looks like an oversight on our part. The base class is has the C++ class exported, but not the inherited classes, meaning they can’t be linked to from other modules.

I can’t think of a great work around, without editing the engine source code to add MOVIERENDERPIPELINERENDERPASSES_API to UMovieGraphImageSequenceOutputNode_JPG, ie:

class MOVIERENDERPIPELINERENDERPASSES_API UMovieGraphImageSequenceOutputNode_JPG : public UMovieGraphImageSequenceOutputNode

If you’re not using a custom engine build, there’s a somewhat unusual possible solution here - copy the Movie Render Pipeline plugin from Engine/Plugins/MovieScene to MYGAME/Plugins, and leave the name the same. Make sure you delete the intermediate data (ie the built dlls) from the plugin folder. The engine should detect this as a plugin for your project and use the game’s version of it over the engine’s version, hopefully without breaking any existing content. You can then change the source code for the plugin locally in your project (to add the _API exports). This isn’t a common method so there may be some unforseen side effects of doing this, but I don’t expect them to result in any data loss in assets.

We’ll look at getting this fixed in the long term but it may be too late to fix it for 5.6 and we may have to wait until 5.7 before we can fix it in the engine.

As to why it works in Python: Python/Blueprints use a different mechanism for exposing things to them - they look for the presence of UCLASS(BlueprintType) somewhere in the hierarchy. This is all handled at runtime though (ie: the default objects are instantiated at runtime and then they register things into runtime reflection information, etc.), as opposed to the C++ version which relies on dllexport/dllimport (which the FOO_API macro is a wrapper for) and has to be handled at link time.

I think this is up to your Python implementation (TestExecutorClass). There is “native” support (which doesn’t require a custom Python executor) for rendering an entire Queue (which should work with both MRQ and MRG jobs in that queue);

`/**

  • Command Line rendering supports two options for rendering things. We support a very simple ‘provide the level sequence/settings to use’
  • option which mimicks the old command line support. Option two is more advanced where you provide your own Executor. To allow for flexibility,
  • the only thing required here is the executor path, a queue/level sequence are optional.
  • Option 1: Simple Command Line Render.
    • Level Sequence (Required unless you pass an entire Queue asset, passed via -LevelSequence=“/Game/…”)
    • Preset or Queue to use (A preset is required if using a Level Sequence above, passed via -MoviePipelineConfig=“/Game/…”)
    • Will render on current map
  • ie:
  • “E:\SubwaySequencer\SubwaySequencer.uproject” subwaySequencer_P -game -LevelSequence=“/Game/Sequencer/SubwaySequencer.SubwaySequencer”
    -MoviePipelineConfig=“/Game/Cinematics/MoviePipeline/Presets/SmallTestPreset.SmallTestPreset” -windowed -resx=1280 -resy=720 -log -notexturestreaming
  • or:
  • ie: “E:\SubwaySequencer\SubwaySequencer.uproject” subwaySequencer_P -game -MoviePipelineConfig=“/Game/Cinematics/MoviePipeline/Presets/BigTestQueue.BigTestQueue”
    -windowed -resx=1280 -resy=720 -log -notexturestreaming
  • Option 2: Advanced Custom Executor.
    • Executor Class (Required, pass via -MoviePipelineLocalExecutorClass=/Script/MovieRenderPipelineCore.MoviePipelinePythonHostExecutor)
    • Level Sequence or Queue (Optional, if passed will be available to Executor)
    • Python Class Override (Optional, requires using MoviePipelinePythonHostExecutor above, pass via -ExecutorPythonClass=/Engine/PythonTypes.MoviePipelineExampleRuntimeExecutor
      */`

ie: If you pass a saved Queue asset with -MoviePipelineConfig, then it should attempt to render the whole queue. But your command line indicates you’re using the second option, where you’re using both MoviePipelinelocalExecutorClass and ExecutorPythonClass, and the engine should kick over into running your Python executor script just with the presence of those two lines. Once started, your Python class can pull any argument from the command line it wants using:

(cmdTokens, cmdSwitches, cmdParameters) = unreal.SystemLibrary.parse_command_line(unreal.SystemLibrary.get_command_line()) levelSequencePath = None try: levelSequencePath = cmdParameters['LevelSequence']

So you could pass a graph asset, and a level sequence, or a queue, etc. See: “\Engine\Plugins\MovieScene\MovieRenderPipeline\Content\Python\MoviePipelineExampleRuntimeExecutor.py” for more examples on how to get parameters off the command line and into jobs from within Python.

Hi Matt,

Thank you for the information. I abandoned the config creation for a time being, for the above mentioned reason. However I noticed some things about the Render Graph, that I hope you could clarify.

  1. When I tried to run the Movie Render Queue in Runtime mode, it seems like there is no option to render via Render Graph as the system expects still UMoviePipelinePrimaryConfig. Will the MovieRenderPipelineCommandLine class be extended so it supports the Movie Render Graph solution as well?
  2. We have some logic in our tools where we change the file name format and output location based on the file format (JPG, EXR). In the previous implementation, I could just look up the config file to see if it has that settings added/enabled and act accordingly. However right now, there is no easy way to get this information apart iterating on all nodes that are corrected to the Output nodes. Also the iteration is quite hard as they have different types that needs to be handled. So my question, will this be easier in the future or we have to setup the MovieRenderGraph with out own user variables so we can pre-determine and filter out these information?

Thanks in advance!

1) Can you clarify on what you mean by Runtime Mode? There’s “launching from the command line with -game”, “render (remote)”, and “I have a shipping build that I’m going to directly create instances of UMoviePipeline and initialize it for rendering.” - in general things that accept a Queue should automatically pick the correct pipeline type based on the job type in the queue itself, since we support mixed queues (ie: you can have both MRQ and MRG jobs within a single queue)

2) Shipping in 5.6 are new utility functions for looking up nodes by class name or scripting tags, ie:

`import unreal

@unreal.uclass()
class ScriptFindNodesTest(unreal.MovieGraphScriptBase):
@unreal.ufunction(override=True)
def on_job_start(self, in_job_copy):
super().on_job_start(in_job_copy)

Get the duplicated graph

graph_config = in_job_copy.get_graph_preset()

unreal.log(“===== FIND NODES TEST (Get Settings For Branch) =====”)

unreal.log(“Finding a node on the Globals branch by Class Type”)
warm_up_node = graph_config.get_setting_for_branch(unreal.MovieGraphWarmUpSettingNode, “Globals”)
unreal.log_warning("Found: " + str(warm_up_node))

unreal.log(“Finding a node on the middle branch by Class Type”)
deferred_renderer_node = graph_config.get_setting_for_branch(unreal.MovieGraphDeferredRenderPassNode, “DefaultLayer”)
unreal.log_warning(“Found: " + str(deferred_renderer_node) + " SS Count: " + str(deferred_renderer_node.spatial_sample_count) + " (should be 5)”)

unreal.log(“Finding a node on the last branch by Class Type”)
deferred_renderer_node2 = graph_config.get_setting_for_branch(unreal.MovieGraphDeferredRenderPassNode, “LastLayer”)
unreal.log_warning(“Found: " + str(deferred_renderer_node2) + " SS Count: " + str(deferred_renderer_node2.spatial_sample_count) + " (should be 3)”)

unreal.log(“Finding multiple settings on Globals by Class Type”)
node_results = graph_config.get_settings_for_branch(unreal.MovieGraphImageSequenceOutputNode_JPG, “Globals”)
unreal.log_warning(“Found: " + str(len(node_results)) + " (should be 2)”)

unreal.log(“Finding multiple settings on middle by Class Type”)
node_results2 = graph_config.get_settings_for_branch(unreal.MovieGraphImageSequenceOutputNode_JPG, “DefaultLayer”)
unreal.log_warning(“Found: " + str(len(node_results2)) + " (should be 1)”)

unreal.log(“Finding multiple settings on last branch by Class Type”)
node_results3 = graph_config.get_settings_for_branch(unreal.MovieGraphImageSequenceOutputNode_JPG, “Globals”)
unreal.log_warning(“Found: " + str(len(node_results3)) + " (should be 2)”)

unreal.log(“===== FIND NODES TEST (Get Settings By Tag) =====”)

unreal.log(“Finding a node on the Globals branch by Tag (No Filter)”)
jpg_node_by_tag = graph_config.get_setting_for_tag(“jpgTag”, None)
unreal.log_warning(“Found: " + str(jpg_node_by_tag) + " (should be anything but None)”)

unreal.log(“Finding a node on the Globals branch by Tag (With Filter)”)
jpg_node_by_tag2 = graph_config.get_setting_for_tag(“jpgTag”, unreal.MovieGraphImageSequenceOutputNode_JPG)
unreal.log_warning(“Found: " + str(jpg_node_by_tag2) + " (should be anything but None)”)

unreal.log(“Finding a node on the Globals branch by Tag (With mismatched Filter)”)
none_tag = graph_config.get_setting_for_tag(“jpgTag”, unreal.MovieGraphWarmUpSettingNode)
unreal.log_warning(“Found: " + str(none_tag) + " (should be None)”)

unreal.log(“Finding all nodes with Tag (No Class Filter, No Branch Filter)”)
all_jpg_tagged_nodes = graph_config.get_settings_for_tag(“jpgTag”, None)
unreal.log_warning(“Found: " + str(len(all_jpg_tagged_nodes)) + " (Should be 4)”)

unreal.log(“Finding all nodes with Tag (With Class Filter, No Branch Filter)”)
all_jpg_tagged_nodes2 = graph_config.get_settings_for_tag(“jpgTag”, unreal.MovieGraphImageSequenceOutputNode_JPG)
unreal.log_warning(“Found: " + str(len(all_jpg_tagged_nodes2)) + " (Should be 3)”)

unreal.log(“Finding all nodes with Tag (No Class Filter, Branch Filter)”)
all_jpg_tagged_nodes3 = graph_config.get_settings_for_tag(“jpgTag”, None, “LastLayer”)
unreal.log_warning(“Found: " + str(len(all_jpg_tagged_nodes3)) + " (Should be 2)”)

unreal.log(“Finding all nodes with Tag (With Class Filter), Branch Filter”)
all_jpg_tagged_nodes4 = graph_config.get_settings_for_tag(“jpgTag”, unreal.MovieGraphImageSequenceOutputNode_JPG, “LastLayer”)
unreal.log_warning(“Found: " + str(len(all_jpg_tagged_nodes4)) + " (Should be 1)”)

unreal.log(“===== TESTS COMPLETE =====”)`

You do still need to be somewhat aware of the graph nature (ie: you have to choose which layer to look on) when looking up settings, but you can also get the array of branches from the config to easily loop through all of them.

Hi Matt,

Thank you for the example!

Regarding the Runtime part, I would like to run the Movie Render Graph from a Commandline command.

"ExampleProjectName/Example_project.uproject" /Game/Test/Shots/sc01/sc01_sh0010/LV_sc01_sh0010.LV_sc01_sh0010 -game -MoviePipelineLocalExecutorClass=/Script/MovieRenderPipelineCore.MoviePipelinePythonHostExecutor -ExecutorPythonClass=/Script/TestExecutorClass. -MoviePipelineConfig="/Game/Test/RenderGraphTest"

I couldn’t find an entry like -MoviePipelineConfig for Movie Graph Config and also if I pass the RenderGraph to theMoviePipelineConfig it will fail. Could you provide one example syntax if it is already out and supporting this feature?

Thanks in advance!

Hi again,

Thank you very much for your suggestion, I customized our very own Executor class to support both render graph and movie render config solution!