diff --git a/Changes.md b/Changes.md index bf2e8353a55..f98bb44f44a 100644 --- a/Changes.md +++ b/Changes.md @@ -16,6 +16,11 @@ Fixes - Arnold : Fixed rendering of `ai:volume` shaders loaded from USD (#5830). +API +--- + +- SceneAlgo : Added mechanism for scoping render adaptors to specific clients and/or renderers. + 1.4.2.0 (relative to 1.4.1.0) ======= diff --git a/include/GafferScene/SceneAlgo.h b/include/GafferScene/SceneAlgo.h index 3c08155fc52..9ac24c75950 100644 --- a/include/GafferScene/SceneAlgo.h +++ b/include/GafferScene/SceneAlgo.h @@ -308,16 +308,39 @@ GAFFERSCENE_API void validateName( IECore::InternedString name ); /// Render Adaptors /// =============== +/// +/// Adaptors are nodes that are implicitly appended to the node graph +/// before rendering, allowing them to make final modifications +/// to the scene. They can be used to do "delayed resolution" of custom +/// features, enforce pipeline policies or customise output for specific +/// renderers or clients. +/// +/// Adaptors are created by clients such as the Render and InteractiveRender +/// nodes and the SceneView which implements Gaffer's 3D Viewer. +/// +/// Adaptors are implemented as SceneProcessors with optional `client` +/// and `renderer` input StringPlugs to inform them of the scope in +/// which they are operating. Custom adaptors can be registered for +/// any purpose by calling `registerRenderAdaptor()`. Examples include +/// `startup/GafferScene/renderSetAdaptor.py` which implements features +/// for the RenderPassEditor, and `startup/GafferArnold/ocio.py` which +/// configures a default colour manager for Arnold. /// Function to return a SceneProcessor used to adapt the /// scene for rendering. using RenderAdaptor = std::function; -/// Registers an adaptor. +/// Registers an adaptor to be applied when `client` renders using `renderer`. Standard +/// wildcards may be used to match multiple clients and/or renderers. +GAFFERSCENE_API void registerRenderAdaptor( const std::string &name, RenderAdaptor adaptor, const std::string &client, const std::string &renderer ); +/// Equivalent to `registerRenderAdaptor( name, adaptor, "*", "*" )`. +/// \todo Remove GAFFERSCENE_API void registerRenderAdaptor( const std::string &name, RenderAdaptor adaptor ); /// Removes a previously registered adaptor. GAFFERSCENE_API void deregisterRenderAdaptor( const std::string &name ); /// Returns a SceneProcessor that will apply all the currently -/// registered adaptors. +/// registered adaptors. It is the client's responsibility to set +/// the adaptor's `client` and `renderer` string plugs to appropriate +/// values before use. GAFFERSCENE_API SceneProcessorPtr createRenderAdaptors(); /// Apply Camera Globals diff --git a/python/GafferSceneTest/SceneAlgoTest.py b/python/GafferSceneTest/SceneAlgoTest.py index c538ead33a7..412ec3bb546 100644 --- a/python/GafferSceneTest/SceneAlgoTest.py +++ b/python/GafferSceneTest/SceneAlgoTest.py @@ -2189,6 +2189,76 @@ def a() : self.assertScenesEqual( defaultAdaptors["out"], defaultAdaptors2["out"] ) self.assertSceneHashesEqual( defaultAdaptors["out"], defaultAdaptors2["out"] ) + def testRenderAdaptorScope( self ) : + + def adaptor() : + + result = GafferScene.CustomOptions() + result["options"].addChild( Gaffer.NameValuePlug( "adapted", True ) ) + return result + + for clientPattern, rendererPattern, client, renderer, expectAdapted in ( + ( "*", "*", None, None, True ), + ( "*", "*", "Render", "Arnold", True ), + ( "*", "Arnold", "Render", "Arnold", True ), + ( "*", "Arnold", "Render", "Cycles", False ), + ( "Render", "*", "Render", "Arnold", True ), + ( "Render", "*", "InteractiveRender", "Arnold", False ), + ( "Render InteractiveRender", "*", "Render", "Arnold", True ), + ( "Render InteractiveRender", "*", "InteractiveRender", "Arnold", True ), + ( "Render InteractiveRender", "*", "SceneView", "Arnold", False ), + ( "Render", "Arnold", "SceneView", "Arnold", False ), + ( "Render", "Arnold", "Render", "Arnold", True ), + ) : + with self.subTest( clientPattern = clientPattern, rendererPattern = rendererPattern, client = client, renderer = renderer ) : + + GafferScene.SceneAlgo.registerRenderAdaptor( "Test", adaptor, clientPattern, rendererPattern ) + self.addCleanup( GafferScene.SceneAlgo.deregisterRenderAdaptor, "Test" ) + + adaptors = GafferScene.SceneAlgo.createRenderAdaptors() + if client is not None : + adaptors["client"].setValue( client ) + if renderer is not None : + adaptors["renderer"].setValue( renderer ) + + if expectAdapted : + self.assertIn( "option:adapted", adaptors["out"].globals() ) + else : + self.assertNotIn( "option:adapted", adaptors["out"].globals() ) + + def testRenderAdaptorScopePlugs( self ) : + + def adaptor() : + + result = GafferScene.CustomOptions() + result["client"] = Gaffer.StringPlug() + result["renderer"] = Gaffer.StringPlug() + + result["options"].addChild( Gaffer.NameValuePlug( "theClient", "" ) ) + result["options"].addChild( Gaffer.NameValuePlug( "theRenderer", "" ) ) + + result["options"][0]["value"].setInput( result["client"] ) + result["options"][1]["value"].setInput( result["renderer"] ) + + return result + + for client, renderer in [ + ( "*", "*" ), + ( "SceneView", "Arnold" ), + ] : + + with self.subTest( client = client, renderer = renderer ) : + + GafferScene.SceneAlgo.registerRenderAdaptor( "Test", adaptor, client, renderer ) + self.addCleanup( GafferScene.SceneAlgo.deregisterRenderAdaptor, "Test" ) + + adaptors = GafferScene.SceneAlgo.createRenderAdaptors() + adaptors["client"].setValue( "SceneView" ) + adaptors["renderer"].setValue( "Arnold" ) + + self.assertEqual( adaptors["out"].globals()["option:theClient"].value, "SceneView" ) + self.assertEqual( adaptors["out"].globals()["option:theRenderer"].value, "Arnold" ) + def testNullAdaptor( self ) : def a() : diff --git a/src/GafferScene/InteractiveRender.cpp b/src/GafferScene/InteractiveRender.cpp index 11bc49a6021..7cfe7327920 100644 --- a/src/GafferScene/InteractiveRender.cpp +++ b/src/GafferScene/InteractiveRender.cpp @@ -172,6 +172,8 @@ InteractiveRender::InteractiveRender( const IECore::InternedString &rendererType SceneProcessorPtr adaptors = SceneAlgo::createRenderAdaptors(); setChild( "__adaptors", adaptors ); adaptors->inPlug()->setInput( inPlug() ); + adaptors->getChild( "client" )->setValue( "InteractiveRender" ); + adaptors->getChild( "renderer" )->setInput( resolvedRendererPlug() ); adaptedInPlug()->setInput( adaptors->outPlug() ); outPlug()->setInput( inPlug() ); diff --git a/src/GafferScene/Render.cpp b/src/GafferScene/Render.cpp index 6c80dbc644c..bd50011ea62 100644 --- a/src/GafferScene/Render.cpp +++ b/src/GafferScene/Render.cpp @@ -119,6 +119,8 @@ Render::Render( const IECore::InternedString &rendererType, const std::string &n SceneProcessorPtr adaptors = GafferScene::SceneAlgo::createRenderAdaptors(); setChild( "__adaptors", adaptors ); adaptors->inPlug()->setInput( inPlug() ); + adaptors->getChild( "client" )->setValue( "Render" ); + adaptors->getChild( "renderer" )->setInput( resolvedRendererPlug() ); adaptedInPlug()->setInput( adaptors->outPlug() ); outPlug()->setInput( inPlug() ); diff --git a/src/GafferScene/SceneAlgo.cpp b/src/GafferScene/SceneAlgo.cpp index 8f1f66606c4..24e30a44015 100644 --- a/src/GafferScene/SceneAlgo.cpp +++ b/src/GafferScene/SceneAlgo.cpp @@ -54,6 +54,7 @@ #include "Gaffer/Context.h" #include "Gaffer/Expression.h" #include "Gaffer/Monitor.h" +#include "Gaffer/NameSwitch.h" #include "Gaffer/Private/IECorePreview/LRUCache.h" #include "Gaffer/Process.h" #include "Gaffer/ScriptNode.h" @@ -1327,7 +1328,14 @@ void GafferScene::SceneAlgo::validateName( IECore::InternedString name ) namespace { -using RenderAdaptors = boost::container::flat_map; +struct RenderAdaptorRegistration +{ + SceneAlgo::RenderAdaptor creator; + std::string client; + std::string renderer; +}; + +using RenderAdaptors = boost::container::flat_map; RenderAdaptors &renderAdaptors() { @@ -1335,11 +1343,69 @@ RenderAdaptors &renderAdaptors() return *a; } +SceneProcessorPtr createRenderAdaptor( const RenderAdaptorRegistration ®istration ) +{ + SceneProcessorPtr adaptor = registration.creator(); + if( !adaptor ) + { + return nullptr; + } + + if( registration.client == "*" && registration.renderer == "*" ) + { + return adaptor; + } + + // Wrap using NameSwitches to enable/disable based on client/renderer. + + SceneProcessorPtr wrapper = new SceneProcessor; + wrapper->addChild( new StringPlug( "client" ) ); + wrapper->addChild( new StringPlug( "renderer" ) ); + wrapper->addChild( adaptor ); + adaptor->inPlug()->setInput( wrapper->inPlug() ); + + ScenePlug *out = adaptor->outPlug(); + + for( const auto &scope : { string( "client" ), string( "renderer" ) } ) + { + if( auto p = adaptor->getChild( scope ) ) + { + p->setInput( wrapper->getChild( scope ) ); + } + + const std::string &scopeValue = scope == "client" ? registration.client : registration.renderer; + if( scopeValue == "*" ) + { + continue; + } + + NameSwitchPtr nameSwitch = new NameSwitch( scope + "Switch" ); + wrapper->addChild( nameSwitch ); + nameSwitch->setup( out ); + nameSwitch->inPlugs()->getChild( 0 )->valuePlug()->setInput( wrapper->inPlug() ); + nameSwitch->inPlugs()->getChild( 1 )->valuePlug()->setInput( out ); + nameSwitch->selectorPlug()->setInput( wrapper->getChild( scope ) ); + /// \todo Remove "unspecified" - see matching comment in `createRenderAdaptors()`. + nameSwitch->inPlugs()->getChild( 1 )->namePlug()->setValue( scopeValue + " unspecified" ); + + out = nameSwitch->outPlug()->getChild( "value" ); + } + + wrapper->outPlug()->setInput( out ); + + return wrapper; +} + } // namespace +void GafferScene::SceneAlgo::registerRenderAdaptor( const std::string &name, RenderAdaptor adaptor, const std::string &client, const std::string &renderer ) +{ + renderAdaptors()[name] = { adaptor, client, renderer }; +} + void GafferScene::SceneAlgo::registerRenderAdaptor( const std::string &name, SceneAlgo::RenderAdaptor adaptor ) { - renderAdaptors()[name] = adaptor; + registerRenderAdaptor( name, adaptor, "*", "*" ); } void GafferScene::SceneAlgo::deregisterRenderAdaptor( const std::string &name ) @@ -1350,26 +1416,44 @@ void GafferScene::SceneAlgo::deregisterRenderAdaptor( const std::string &name ) SceneProcessorPtr GafferScene::SceneAlgo::createRenderAdaptors() { SceneProcessorPtr result = new SceneProcessor; + StringPlugPtr clientPlug = new StringPlug( "client" ); + StringPlugPtr rendererPlug = new StringPlug( "renderer" ); + result->addChild( clientPlug ); + result->addChild( rendererPlug ); + + /// \todo Remove, and require all clients to set the plugs themselves. + clientPlug->setValue( "unspecified" ); + rendererPlug->setValue( "unspecified" ); ScenePlug *in = result->inPlug(); - const RenderAdaptors &a = renderAdaptors(); - for( RenderAdaptors::const_iterator it = a.begin(), eIt = a.end(); it != eIt; ++it ) + const RenderAdaptors &adaptors = renderAdaptors(); + for( const auto &[name, a] : adaptors ) { - SceneProcessorPtr adaptor = it->second(); - if( adaptor ) - { - result->addChild( adaptor ); - adaptor->inPlug()->setInput( in ); - in = adaptor->outPlug(); - } - else + SceneProcessorPtr adaptor = createRenderAdaptor( a ); + if( !adaptor ) { IECore::msg( IECore::Msg::Warning, "SceneAlgo::createRenderAdaptors", - fmt::format( "Adaptor \"{}\" returned null", it->first ) + fmt::format( "Adaptor \"{}\" returned null", name ) ); + continue; + } + + adaptor->setName( name ); + if( auto s = adaptor->getChild( "client" ) ) + { + s->setInput( clientPlug.get() ); } + + if( auto s = adaptor->getChild( "renderer" ) ) + { + s->setInput( rendererPlug.get() ); + } + + result->addChild( adaptor ); + adaptor->inPlug()->setInput( in ); + in = adaptor->outPlug(); } result->outPlug()->setInput( in ); diff --git a/src/GafferSceneModule/SceneAlgoBinding.cpp b/src/GafferSceneModule/SceneAlgoBinding.cpp index 93a4ae8ee01..957f1be90d2 100644 --- a/src/GafferSceneModule/SceneAlgoBinding.cpp +++ b/src/GafferSceneModule/SceneAlgoBinding.cpp @@ -340,9 +340,9 @@ struct RenderAdaptorWrapper }; -void registerRenderAdaptorWrapper( const std::string &name, object adaptor ) +void registerRenderAdaptorWrapper( const std::string &name, object adaptor, const std::string &client, const std::string &renderer ) { - SceneAlgo::registerRenderAdaptor( name, RenderAdaptorWrapper( adaptor ) ); + SceneAlgo::registerRenderAdaptor( name, RenderAdaptorWrapper( adaptor ), client, renderer ); } void applyCameraGlobalsWrapper( IECoreScene::Camera &camera, const IECore::CompoundObject &globals, const ScenePlug &scene ) @@ -441,7 +441,7 @@ void bindSceneAlgo() // Render adaptors - def( "registerRenderAdaptor", ®isterRenderAdaptorWrapper ); + def( "registerRenderAdaptor", ®isterRenderAdaptorWrapper, ( arg( "name" ), arg( "adaptor" ), arg( "client" ) = "*", arg( "renderer" ) = "*" ) ); def( "deregisterRenderAdaptor", &SceneAlgo::deregisterRenderAdaptor ); def( "createRenderAdaptors", &SceneAlgo::createRenderAdaptors ); diff --git a/src/GafferSceneUI/SceneView.cpp b/src/GafferSceneUI/SceneView.cpp index e4fba02038e..6f335185020 100644 --- a/src/GafferSceneUI/SceneView.cpp +++ b/src/GafferSceneUI/SceneView.cpp @@ -2015,6 +2015,7 @@ SceneView::SceneView( const std::string &name ) SceneProcessorPtr adaptors = SceneAlgo::createRenderAdaptors(); preprocessor->addChild( adaptors ); + adaptors->getChild( "client" )->setValue( "SceneView" ); adaptors->inPlug()->setInput( deleteObject->outPlug() ); // add in the node from the ShadingMode @@ -2031,6 +2032,7 @@ SceneView::SceneView( const std::string &name ) preprocessor->addChild( m_renderer->preprocessor() ); m_renderer->preprocessor()->inPlug()->setInput( m_drawingMode->preprocessor()->outPlug() ); + adaptors->getChild( "renderer" )->setInput( getChild( "renderer" )->getChild( "name" ) ); // remove motion blur, because the opengl renderer doesn't support it.