Hi!
I am trying to setup the rendering purely with c++ without using scripts. In editor like environment, the compositor is dynamic
Personally I suggest that you have a set of compositor nodes created via scripts that you can edit via C++ or connect at runtime using the workspace.
Simply because the scripts are much easier to work with, while the C++ interface is hard to deal with.
Code: Select all
auto* pOgreCompositorPassClear = dynamic_cast<Ogre::CompositorPassClear*>(m_pOgreCompositorWorkspace->getNodeSequence()[0]->_getPasses().front());
if (pOgreCompositorPassClear == nullptr)
return;
You can use compositorPass->getDefinition()->getType() to determine which type of pass you're dealing with (rather than downcasting via dynamic_cast).
Am I missing something? Should I create always a unique Ogre::CompositorWorkspaceDef/Ogre::CompositorWorkspace for every window? Consider to have 4 windows in the editor - according to the "view type" (front, top, left, etc.) I would like to set custom color to indicate the camera orientation.
HdrUtils::setSkyColour in the samples shows how to change the clear colour and indeed for maximum compatibility you're supposed to change the definition too and not just the RenderPassDescriptor.
You have various options:
- Use only one copy and use
pass->getRenderPassDesc()->mColour[0].texture->addListener() (where pass is Ogre::CompositorPass) to listen to TextureGpu changes. You will see that the texture transitions to OnStorage and then to Resident again in CompositorNode::finalTargetResized01. Shortly after this CompositorNode::finalTargetResized02 will recreate the RenderPassDescriptor. Perhaps we could add a listener in finalTargetResized02 to make this much easier?.
- Create multiple clones in its own node (I'll take more about this down below).
Creating multiple clones
I'm going to explain this mostly because I said "I suggest that you have a set of compositor nodes created via scripts that you can edit via C++ or connect at runtime using the workspace". This is how I suggest you approach most of Compositor problems:
Code: Select all
// Clearing in its own pass isn't mobile-friendly, however this
// is flexible for an editor.
//
// You can create this particular node in C++
compositor_node ClearNode
{
in 0 rt_renderwindow
target rt_renderwindow
{
pass clear
{
load
{
colour clear
depth dont_care
stencil dont_care
}
store
{
colour store_or_resolve
depth dont_care
stencil dont_care
}
profiling_id "Clear Pass"
}
}
out 0 rt_renderwindow
}
compositor_node MainRenderingNode
{
in 0 rt_renderwindow
target rt_renderwindow
{
pass render_scene
{
load
{
colour load
depth clear
stencil clear
}
store
{
colour store_or_resolve
depth dont_care
stencil dont_care
}
profiling_id "Scene Pass"
identifier 54 // Number is arbitrary but must be in sync with C++
rq_first 1
rq_last 10
}
}
out 0 rt_renderwindow
}
compositor_node DebugRenderingNode
{
in 0 rt_renderwindow
target rt_renderwindow
{
pass render_scene
{
load
{
colour load
depth clear
stencil clear
}
store
{
colour store_or_resolve
depth dont_care
stencil dont_care
}
profiling_id "Selected Objects"
identifier 55 // Number is arbitrary but must be in sync with C++
rq_first 10
rq_last 20
}
pass render_scene
{
load
{
colour load
depth clear
stencil clear
}
store
{
colour store_or_resolve
depth dont_care
stencil dont_care
}
profiling_id "Top Layer"
identifier 56 // Number is arbitrary but must be in sync with C++
rq_first 20
rq_last 30
}
}
}
// The workspace will also be created in C++
workspace ExampleWorkspace
{
connect_external 0 ClearNode 0
connect ClearNode 0 MainRenderingNode 0
connect MainRenderingNode 0 DebugRenderingNode 0
}
In this example you can create ClearNode & ExampleWorkspace in C++:
Code: Select all
createClearNode(); // See Postprocessing example on how to create a nodes and passes programmatically.
Ogre::CompositorWorkspaceDef *workspaceDef = compositorManager->addWorkspaceDefinition( "MyRuntimeWorkspace" );
workspaceDef->connectExternal( 0u, "MyRuntimeClearNode", 0u );
workspaceDef->connect( "MyRuntimeClearNode", 0u, "MainRenderingNode", 0u );
if( debug_view_on )
workspaceDef->connect( "MainRenderingNode", 0u, "DebugRenderingNode", 0u );
Thus this way, having blocks of pre-made nodes which you mix and match programmatically is much simpler than creating every single pass programmatically.
Other ideas: Using execution masks
For toggling your debug views you could use execution masks instead:
Code: Select all
compositor_node ExampleRenderingNode
{
in 0 rt_renderwindow
target rt_renderwindow
{
pass render_scene
{
load
{
colour load // We assume it's cleared by your runtime-generated clear node
depth clear
stencil clear
}
store
{
colour store_or_resolve
depth dont_care
stencil dont_care
}
profiling_id "Scene Pass"
identifier 54 // Number is arbitrary but must be in sync with C++
rq_first 1
rq_last 10
}
pass render_scene
{
execution_mask 0x02
load
{
colour load
depth clear
stencil clear
}
store
{
colour store_or_resolve
depth dont_care
stencil dont_care
}
profiling_id "Selected Objects"
identifier 55 // Number is arbitrary but must be in sync with C++
rq_first 10
rq_last 20
}
pass render_scene
{
execution_mask 0x04
load
{
colour load
depth clear
stencil clear
}
store
{
colour store_or_resolve
depth dont_care
stencil dont_care
}
profiling_id "Top Layer"
identifier 56 // Number is arbitrary but must be in sync with C++
rq_first 20
rq_last 30
}
}
}
Notice the "execution_mask" on the last two passes. Then from C++:
Code: Select all
if( select_objs_layer )
mask |= 0x02;
if( top_layer )
mask |= 0x04;
workspace->setExecutionMask( mask );
workspace->_notifyBarriersDirty(); // Just in case, for maximum Vulkan compatibility
The great advantage of this method is that you don't recreate resources (destroying a workspace and creating it again recreates textures), so it's much cheaper to toggle.
You can also combine node compositing as shown in the previous section, with execution masks. They're not mutually exclusive.