From 670fe4a991e4c6fe6aef5a82b18db7557ca97e3b Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 22 Sep 2023 11:10:13 +0100 Subject: [PATCH 1/8] RenderController : Tweak camera shutter change tracking The dirty flags track things that may no longer be in sync with the Gaffer scene, and therefore require an update. The changed flags track things that actually changed when we performed that update. Use this consistently for camera shutter updates, and improve comment to clarify why we're doing what we're doing. As far as I can tell, this doesn't affect the correctness of any outcome, but it does mean we'll avoid some redundant work when restarting after a cancellation part way through an update. This is because we clear the dirty flags as soon as we've updated the globals, but don't clear the changed flags until we've updated the whole scene. --- src/GafferScene/RenderController.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/GafferScene/RenderController.cpp b/src/GafferScene/RenderController.cpp index 9b2a89fd5d0..773b4e6e699 100644 --- a/src/GafferScene/RenderController.cpp +++ b/src/GafferScene/RenderController.cpp @@ -1564,10 +1564,9 @@ void RenderController::dirtySceneGraphs( unsigned components ) if( components & SceneGraph::ObjectComponent ) { - // We don't track dirtiness of different SceneGraphs separately anyway, - // so just recheck if a camera has changed a shutter override whenever - // any object is dirtied - m_changedGlobalComponents |= CameraShutterGlobalComponent; + // Changes to a camera object may include changing the + // shutter that overrides the global shutter. + m_dirtyGlobalComponents |= CameraShutterGlobalComponent; } } @@ -1653,7 +1652,7 @@ void RenderController::updateInternal( const ProgressCallback &callback, const I } // Update motion blur options - if( m_changedGlobalComponents & ( GlobalsGlobalComponent | CameraShutterGlobalComponent ) ) + if( ( m_changedGlobalComponents & GlobalsGlobalComponent ) || ( m_dirtyGlobalComponents & CameraShutterGlobalComponent ) ) { const BoolData *transformBlurData = m_globals->member( g_transformBlurOptionName ); bool transformBlur = transformBlurData ? transformBlurData->readable() : false; From d7addd1ff69026bfb5f72dfcae225ecc3995ed86 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Mon, 25 Sep 2023 14:20:52 +0100 Subject: [PATCH 2/8] RenderController : Remove redundant conditional We know that type isn't LightType or LightFilter type already, because there is a return statement for that case at line 856. I've also replaced the non-const `isNull` variable with a test directly inside the `if`, so there's one less piece of state to have to bear in mind throughout the rest of the function (where the variable isn't actually used again). --- src/GafferScene/RenderController.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/GafferScene/RenderController.cpp b/src/GafferScene/RenderController.cpp index 773b4e6e699..5a6157c992e 100644 --- a/src/GafferScene/RenderController.cpp +++ b/src/GafferScene/RenderController.cpp @@ -882,7 +882,6 @@ class RenderController::SceneGraph } return true; - } vector samples; @@ -891,16 +890,12 @@ class RenderController::SceneGraph return false; } - bool isNull = true; - for( ConstObjectPtr &i : samples ) - { - if( !runTimeCast( i.get() ) ) - { - isNull = false; - } - } - - if( (type != LightType && type != LightFilterType) && isNull ) + if( + std::all_of( + samples.begin(), samples.end(), + [] ( const ConstObjectPtr &sample ) { return runTimeCast( sample.get() ); } + ) + ) { m_objectInterface = nullptr; return hadObjectInterface; From c27493f005dd3ee17cbc340b5e3f1a7415ec5284 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 20 Sep 2023 15:04:55 +0100 Subject: [PATCH 3/8] Capsule/RendererAlgo : Don't bake global attributes into Capsule contents --- Changes.md | 1 + python/GafferSceneTest/CapsuleTest.py | 64 +++++++++++++++++++++++++++ src/GafferScene/RendererAlgo.cpp | 3 +- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Changes.md b/Changes.md index d57c39ebaae..4e40cbe6a27 100644 --- a/Changes.md +++ b/Changes.md @@ -17,6 +17,7 @@ Fixes - StringPlugValueWidget : Fixed bug handling Esc. - Arnold : Fixed unnecessary `opaque` attribute deprecation warnings. These are now only emitted in the case that `opaque` has been explicitly turned off. - ShaderUI : Fixed bug causing identical but independent shaders in a shader network from being included in the shader browser. +- Encapsulate : Fixed bug where global attributes (from the point of encapsulation) were baked into the contents of the capsule instead of being inherited naturally (from the node being rendered). API --- diff --git a/python/GafferSceneTest/CapsuleTest.py b/python/GafferSceneTest/CapsuleTest.py index 96d05c41562..a17a2b92c7f 100644 --- a/python/GafferSceneTest/CapsuleTest.py +++ b/python/GafferSceneTest/CapsuleTest.py @@ -72,5 +72,69 @@ def test( self ) : self.assertEqual( capsuleCopy.bound(), sphere["out"].bound( "/" ) ) self.assertEqual( capsuleCopy.hash(), capsule.hash() ) + def testInheritedAttributesAreNotBakedIntoCapsuleContents( self ) : + + # Make a Capsule which inherits attributes from its parent + # and from the scene globals. + + sphere = GafferScene.Sphere() + group = GafferScene.Group() + group["in"][0].setInput( sphere["out"] ) + + groupFilter = GafferScene.PathFilter() + groupFilter["paths"].setValue( IECore.StringVectorData( [ "/group" ] ) ) + + groupAttributes = GafferScene.CustomAttributes() + groupAttributes["in"].setInput( group["out"] ) + groupAttributes["filter"].setInput( groupFilter["out"] ) + groupAttributes["attributes"].addChild( Gaffer.NameValuePlug( "groupAttribute", 10 ) ) + + globalAttributes = GafferScene.CustomAttributes() + globalAttributes["in"].setInput( groupAttributes["out"] ) + globalAttributes["global"].setValue( True ) + globalAttributes["attributes"].addChild( Gaffer.NameValuePlug( "globalAttribute", 20 ) ) + + encapsulate = GafferScene.Encapsulate() + encapsulate["in"].setInput( globalAttributes["out"] ) + encapsulate["filter"].setInput( groupFilter["out"] ) + + # Render it, and check that the capsule object had the inherited attributes applied to it. + + renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch + ) + GafferScene.Private.RendererAlgo.outputObjects( + encapsulate["out"], encapsulate["out"].globals(), GafferScene.Private.RendererAlgo.RenderSets( encapsulate["out"] ), GafferScene.Private.RendererAlgo.LightLinks(), + renderer + ) + + capturedGroup = renderer.capturedObject( "/group" ) + self.assertIsInstance( capturedGroup.capturedSamples()[0], GafferScene.Capsule ) + self.assertEqual( + capturedGroup.capturedAttributes().attributes(), + IECore.CompoundObject( { + "groupAttribute" : IECore.IntData( 10 ), + "globalAttribute" : IECore.IntData( 20 ), + "sets" : IECore.InternedStringVectorData(), + } ) + ) + + # Expand the capsule, and check that it didn't bake the inherited attributes onto + # its contents. It is the responsibity of the Renderer itself to take care of attribute + # inheritance, ideally doing it "live", so that changes to inherited attributes don't + # require re-expansion of the capsule. + + renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch + ) + capturedGroup.capturedSamples()[0].render( renderer ) + capturedSphere = renderer.capturedObject( "/sphere" ) + self.assertEqual( + capturedSphere.capturedAttributes().attributes(), + IECore.CompoundObject( { + "sets" : IECore.InternedStringVectorData(), + } ) + ) + if __name__ == "__main__": unittest.main() diff --git a/src/GafferScene/RendererAlgo.cpp b/src/GafferScene/RendererAlgo.cpp index 836614b6ca7..7d46517a5f4 100644 --- a/src/GafferScene/RendererAlgo.cpp +++ b/src/GafferScene/RendererAlgo.cpp @@ -1037,7 +1037,8 @@ struct LocationOutput { LocationOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene ) - : m_renderer( renderer ), m_attributes( SceneAlgo::globalAttributes( globals ) ), m_renderSets( renderSets ), m_root( root ) + : m_renderer( renderer ), m_attributes( root.empty() ? SceneAlgo::globalAttributes( globals ) : new CompoundObject ), + m_renderSets( renderSets ), m_root( root ) { const BoolData *transformBlurData = globals->member( g_transformBlurOptionName ); m_options.transformBlur = transformBlurData ? transformBlurData->readable() : false; From 8c128057532400b72fce561d0ba0a21b77500342 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 22 Sep 2023 16:05:39 +0100 Subject: [PATCH 4/8] CapsuleTest : Remove unused import --- python/GafferSceneTest/CapsuleTest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/GafferSceneTest/CapsuleTest.py b/python/GafferSceneTest/CapsuleTest.py index a17a2b92c7f..ab38f100a98 100644 --- a/python/GafferSceneTest/CapsuleTest.py +++ b/python/GafferSceneTest/CapsuleTest.py @@ -34,7 +34,6 @@ # ########################################################################## -import inspect import unittest import IECore From b16f236b2618035d77073419ad5b5b8b42708334 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 22 Sep 2023 12:15:41 +0100 Subject: [PATCH 5/8] RendererAlgo : Consolidate options handling Both RenderController and LocationOutput had a little class for dealing with the most important rendering options. Consolidating them into a single shared RenderOptions class lets us remove a bit of duplicate code and simplify a few function signatures. But the main purpose of this rejig is to set us up so that we can pass the RenderOptions to Capsules so that they respect the correct motion blur settings. --- include/GafferScene/Private/RendererAlgo.h | 37 ++++-- include/GafferScene/RenderController.h | 10 +- python/GafferSceneTest/CapsuleTest.py | 2 +- python/GafferSceneTest/RendererAlgoTest.py | 27 ++-- src/GafferScene/Capsule.cpp | 4 +- src/GafferScene/Render.cpp | 18 +-- src/GafferScene/RenderController.cpp | 95 +++++-------- src/GafferScene/RendererAlgo.cpp | 147 ++++++++++++--------- src/GafferSceneModule/RenderBinding.cpp | 21 ++- 9 files changed, 187 insertions(+), 174 deletions(-) diff --git a/include/GafferScene/Private/RendererAlgo.h b/include/GafferScene/Private/RendererAlgo.h index de0b599bb3b..34e9697c26b 100644 --- a/include/GafferScene/Private/RendererAlgo.h +++ b/include/GafferScene/Private/RendererAlgo.h @@ -63,14 +63,33 @@ namespace Private namespace RendererAlgo { +struct GAFFERSCENE_API RenderOptions +{ + RenderOptions(); + RenderOptions( const ScenePlug *scene ); + RenderOptions( const RenderOptions &other ) = default; + RenderOptions& operator=( const RenderOptions &other ) = default; + /// The globals from the scene. + IECore::ConstCompoundObjectPtr globals; + /// Convenient access to specific properties, taking into account default + /// values if they have not been specified in the scene. + bool transformBlur; + bool deformationBlur; + Imath::V2f shutter; + IECore::ConstStringVectorDataPtr includedPurposes; + /// Returns true if `includedPurposes` includes the purpose defined by + /// `attributes`. + bool purposeIncluded( const IECore::CompoundObject *attributes ) const; +}; + /// Creates the directories necessary to receive the outputs defined in globals. GAFFERSCENE_API void createOutputDirectories( const IECore::CompoundObject *globals ); -/// Set the "times" to a list of times to sample the transform or deformation of a location at, based on the -/// "motionBlur" enable coming from the options, a shutter, and location attributes. Returns a boolean for -/// whether times has been altered ( returns false if times was already set correctly ). -GAFFERSCENE_API bool transformMotionTimes( bool motionBlur, const Imath::V2f &shutter, const IECore::CompoundObject *attributes, std::vector × ); -GAFFERSCENE_API bool deformationMotionTimes( bool motionBlur, const Imath::V2f &shutter, const IECore::CompoundObject *attributes, std::vector × ); +/// Sets `times` to a list of times to sample the transform or deformation of a +/// location at, based on the render options and location attributes. Returns `true` +/// if `times` was altered and `false` if it was already set correctly. +GAFFERSCENE_API bool transformMotionTimes( const RenderOptions &renderOptions, const IECore::CompoundObject *attributes, std::vector × ); +GAFFERSCENE_API bool deformationMotionTimes( const RenderOptions &renderOptions, const IECore::CompoundObject *attributes, std::vector × ); /// Samples the local transform from the current location in preparation for output to the renderer. /// "samples" will be set to contain one sample for each sampleTime, unless the samples are all identical, @@ -259,10 +278,10 @@ class GAFFERSCENE_API LightLinks : boost::noncopyable }; -GAFFERSCENE_API void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer ); -GAFFERSCENE_API void outputLightFilters( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ); -GAFFERSCENE_API void outputLights( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ); -GAFFERSCENE_API void outputObjects( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); +GAFFERSCENE_API void outputCameras( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer ); +GAFFERSCENE_API void outputLightFilters( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ); +GAFFERSCENE_API void outputLights( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ); +GAFFERSCENE_API void outputObjects( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); } // namespace RendererAlgo diff --git a/include/GafferScene/RenderController.h b/include/GafferScene/RenderController.h index ecbed4a5bf8..66b23656a3f 100644 --- a/include/GafferScene/RenderController.h +++ b/include/GafferScene/RenderController.h @@ -125,13 +125,6 @@ class GAFFERSCENE_API RenderController : public Gaffer::Signals::Trackable AllGlobalComponents = GlobalsGlobalComponent | SetsGlobalComponent | RenderSetsGlobalComponent | CameraOptionsGlobalComponent | TransformBlurGlobalComponent | DeformationBlurGlobalComponent | IncludedPurposesGlobalComponent }; - struct MotionBlurOptions - { - bool transformBlur = false; - bool deformationBlur = false; - Imath::V2f shutter = Imath::V2f( 0 ); - }; - void plugDirtied( const Gaffer::Plug *plug ); void contextChanged( const IECore::InternedString &name ); void requestUpdate(); @@ -165,8 +158,7 @@ class GAFFERSCENE_API RenderController : public Gaffer::Signals::Trackable std::vector > m_sceneGraphs; unsigned m_dirtyGlobalComponents; unsigned m_changedGlobalComponents; - IECore::ConstCompoundObjectPtr m_globals; - MotionBlurOptions m_motionBlurOptions; + Private::RendererAlgo::RenderOptions m_renderOptions; Private::RendererAlgo::RenderSets m_renderSets; std::unique_ptr m_lightLinks; IECoreScenePreview::Renderer::ObjectInterfacePtr m_defaultCamera; diff --git a/python/GafferSceneTest/CapsuleTest.py b/python/GafferSceneTest/CapsuleTest.py index ab38f100a98..c99934be68f 100644 --- a/python/GafferSceneTest/CapsuleTest.py +++ b/python/GafferSceneTest/CapsuleTest.py @@ -103,7 +103,7 @@ def testInheritedAttributesAreNotBakedIntoCapsuleContents( self ) : GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) GafferScene.Private.RendererAlgo.outputObjects( - encapsulate["out"], encapsulate["out"].globals(), GafferScene.Private.RendererAlgo.RenderSets( encapsulate["out"] ), GafferScene.Private.RendererAlgo.LightLinks(), + encapsulate["out"], GafferScene.Private.RendererAlgo.RenderOptions( encapsulate["out"] ), GafferScene.Private.RendererAlgo.RenderSets( encapsulate["out"] ), GafferScene.Private.RendererAlgo.LightLinks(), renderer ) diff --git a/python/GafferSceneTest/RendererAlgoTest.py b/python/GafferSceneTest/RendererAlgoTest.py index ef651635f21..9829c90af34 100644 --- a/python/GafferSceneTest/RendererAlgoTest.py +++ b/python/GafferSceneTest/RendererAlgoTest.py @@ -97,6 +97,7 @@ def testOutputCameras( self ) : options = GafferScene.StandardOptions() options["in"].setInput( camera["out"] ) + renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( options["out"] ) renderSets = GafferScene.Private.RendererAlgo.RenderSets( options["out"] ) def expectedCamera( frame ) : @@ -105,7 +106,7 @@ def expectedCamera( frame ) : c.setFrame( frame ) camera = options["out"].object( "/camera" ) - GafferScene.SceneAlgo.applyCameraGlobals( camera, sceneGlobals, options["out"] ) + GafferScene.SceneAlgo.applyCameraGlobals( camera, options["out"].globals(), options["out"] ) return camera # Non-animated case @@ -113,8 +114,7 @@ def expectedCamera( frame ) : renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) - sceneGlobals = options["out"].globals() - GafferScene.Private.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer ) + GafferScene.Private.RendererAlgo.outputCameras( options["out"], renderOptions, renderSets, renderer ) capturedCamera = renderer.capturedObject( "/camera" ) @@ -129,8 +129,8 @@ def expectedCamera( frame ) : renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) - sceneGlobals = options["out"].globals() - GafferScene.Private.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer ) + renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( options["out"] ) + GafferScene.Private.RendererAlgo.outputCameras( options["out"], renderOptions, renderSets, renderer ) capturedCamera = renderer.capturedObject( "/camera" ) self.assertEqual( capturedCamera.capturedSamples(), [ expectedCamera( 0.75 ), expectedCamera( 1.25 ) ] ) @@ -155,7 +155,8 @@ def testInvisibleCamera( self ) : ) with self.assertRaisesRegex( RuntimeError, "Camera \"/camera\" is hidden" ) : GafferScene.Private.RendererAlgo.outputCameras( - standardOptions["out"], standardOptions["out"].globals(), + standardOptions["out"], + GafferScene.Private.RendererAlgo.RenderOptions( standardOptions["out"] ), GafferScene.Private.RendererAlgo.RenderSets( standardOptions["out"] ), renderer ) @@ -317,14 +318,14 @@ def testLightSolo( self ) : # Output the lights to the renderer + renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( soloSet["out"] ) renderSets = GafferScene.Private.RendererAlgo.RenderSets( soloSet["out"] ) lightLinks = GafferScene.Private.RendererAlgo.LightLinks() renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) - sceneGlobals = soloSet["out"].globals() - GafferScene.Private.RendererAlgo.outputLights( soloSet["out"], sceneGlobals, renderSets, lightLinks, renderer ) + GafferScene.Private.RendererAlgo.outputLights( soloSet["out"], renderOptions, renderSets, lightLinks, renderer ) # Check that the output is correct @@ -448,14 +449,14 @@ def testLightMute( self ) : # Output the lights to the renderer + renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( muteAttributes["out"] ) renderSets = GafferScene.Private.RendererAlgo.RenderSets( muteAttributes["out"] ) lightLinks = GafferScene.Private.RendererAlgo.LightLinks() renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) - sceneGlobals = muteAttributes["out"].globals() - GafferScene.Private.RendererAlgo.outputLights( muteAttributes["out"], sceneGlobals, renderSets, lightLinks, renderer ) + GafferScene.Private.RendererAlgo.outputLights( muteAttributes["out"], renderOptions, renderSets, lightLinks, renderer ) # Check that the output is correct @@ -643,15 +644,15 @@ def purposeAttribute( purpose ) : def assertIncludedObjects( scene, includedPurposes, paths ) : - globals = IECore.CompoundObject() + renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( scene ) if includedPurposes : - globals["option:render:includedPurposes"] = IECore.StringVectorData( includedPurposes ) + renderOptions.includedPurposes = IECore.StringVectorData( includedPurposes ) renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) GafferScene.Private.RendererAlgo.outputObjects( - group["out"], globals, GafferScene.Private.RendererAlgo.RenderSets( scene ), GafferScene.Private.RendererAlgo.LightLinks(), + group["out"], renderOptions, GafferScene.Private.RendererAlgo.RenderSets( scene ), GafferScene.Private.RendererAlgo.LightLinks(), renderer ) diff --git a/src/GafferScene/Capsule.cpp b/src/GafferScene/Capsule.cpp index 9f1fa54b6dc..dfc8b9133ee 100644 --- a/src/GafferScene/Capsule.cpp +++ b/src/GafferScene/Capsule.cpp @@ -147,9 +147,9 @@ void Capsule::render( IECoreScenePreview::Renderer *renderer ) const { throwIfNoScene(); ScenePlug::GlobalScope scope( m_context.get() ); - IECore::ConstCompoundObjectPtr globals = m_scene->globalsPlug()->getValue(); + GafferScene::Private::RendererAlgo::RenderOptions renderOptions( m_scene ); GafferScene::Private::RendererAlgo::RenderSets renderSets( m_scene ); - GafferScene::Private::RendererAlgo::outputObjects( m_scene, globals.get(), renderSets, /* lightLinks = */ nullptr, renderer, m_root ); + GafferScene::Private::RendererAlgo::outputObjects( m_scene, renderOptions, renderSets, /* lightLinks = */ nullptr, renderer, m_root ); } const ScenePlug *Capsule::scene() const diff --git a/src/GafferScene/Render.cpp b/src/GafferScene/Render.cpp index 4c2908f02af..1dd32b7cf46 100644 --- a/src/GafferScene/Render.cpp +++ b/src/GafferScene/Render.cpp @@ -297,14 +297,14 @@ void Render::executeInternal( bool flushCaches ) const return; } - ConstCompoundObjectPtr globals = adaptedInPlug()->globalsPlug()->getValue(); + GafferScene::Private::RendererAlgo::RenderOptions renderOptions( adaptedInPlug() ); if( !renderScope.sceneTranslationOnly() ) { - GafferScene::Private::RendererAlgo::createOutputDirectories( globals.get() ); + GafferScene::Private::RendererAlgo::createOutputDirectories( renderOptions.globals.get() ); } PerformanceMonitorPtr performanceMonitor; - if( const BoolData *d = globals->member( g_performanceMonitorOptionName ) ) + if( const BoolData *d = renderOptions.globals->member( g_performanceMonitorOptionName ) ) { if( d->readable() ) { @@ -313,8 +313,8 @@ void Render::executeInternal( bool flushCaches ) const } Monitor::Scope performanceMonitorScope( performanceMonitor ); - GafferScene::Private::RendererAlgo::outputOptions( globals.get(), renderer.get() ); - GafferScene::Private::RendererAlgo::outputOutputs( inPlug(), globals.get(), renderer.get() ); + GafferScene::Private::RendererAlgo::outputOptions( renderOptions.globals.get(), renderer.get() ); + GafferScene::Private::RendererAlgo::outputOutputs( inPlug(), renderOptions.globals.get(), renderer.get() ); { // Using nested scope so that we free the memory used by `renderSets` @@ -322,11 +322,11 @@ void Render::executeInternal( bool flushCaches ) const GafferScene::Private::RendererAlgo::RenderSets renderSets( adaptedInPlug() ); GafferScene::Private::RendererAlgo::LightLinks lightLinks; - GafferScene::Private::RendererAlgo::outputCameras( adaptedInPlug(), globals.get(), renderSets, renderer.get() ); - GafferScene::Private::RendererAlgo::outputLights( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); - GafferScene::Private::RendererAlgo::outputLightFilters( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); + GafferScene::Private::RendererAlgo::outputCameras( adaptedInPlug(), renderOptions, renderSets, renderer.get() ); + GafferScene::Private::RendererAlgo::outputLights( adaptedInPlug(), renderOptions, renderSets, &lightLinks, renderer.get() ); + GafferScene::Private::RendererAlgo::outputLightFilters( adaptedInPlug(), renderOptions, renderSets, &lightLinks, renderer.get() ); lightLinks.outputLightFilterLinks( adaptedInPlug() ); - GafferScene::Private::RendererAlgo::outputObjects( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); + GafferScene::Private::RendererAlgo::outputObjects( adaptedInPlug(), renderOptions, renderSets, &lightLinks, renderer.get() ); } if( renderScope.sceneTranslationOnly() ) diff --git a/src/GafferScene/RenderController.cpp b/src/GafferScene/RenderController.cpp index 5a6157c992e..5fa551b6849 100644 --- a/src/GafferScene/RenderController.cpp +++ b/src/GafferScene/RenderController.cpp @@ -110,34 +110,6 @@ bool cameraGlobalsChanged( const CompoundObject *globals, const CompoundObject * return *camera1 != *camera2; } -const IECore::ConstStringVectorDataPtr g_defaultIncludedPurposes = new StringVectorData( { "default", "render", "proxy", "guide" } ); - -bool includedPurposesChanged( const CompoundObject *globals, const CompoundObject *previousGlobals ) -{ - if( !previousGlobals ) - { - return true; - } - - auto *v1 = globals->member( g_includedPurposesOptionName ); - v1 = v1 ? v1 : g_defaultIncludedPurposes.get(); - - auto *v2 = previousGlobals->member( g_includedPurposesOptionName ); - v2 = v2 ? v2 : g_defaultIncludedPurposes.get(); - - return *v1 != *v2; -} - -bool purposeIncluded( const CompoundObject *attributes, const CompoundObject *globals ) -{ - const auto purposeData = attributes->member( g_purposeAttributeName ); - const std::string &purpose = purposeData ? purposeData->readable() : g_defaultPurpose; - - const auto includedPurposesData = globals->member( g_includedPurposesOptionName ); - const vector &includedPurposes = includedPurposesData ? includedPurposesData->readable() : g_defaultIncludedPurposes->readable(); - return std::find( includedPurposes.begin(), includedPurposes.end(), purpose ) != includedPurposes.end(); -} - // This is for the very specific case of determining change for global // attributes, where we need to avoid comparisons of certain synthetic members // that are only present in previousFullAttributes. @@ -399,7 +371,7 @@ class RenderController::SceneGraph // Root - get attributes from globals. if( changedGlobals & GlobalsGlobalComponent ) { - if( updateAttributes( controller->m_globals.get() ) ) + if( updateAttributes( controller->m_renderOptions.globals.get() ) ) { m_changedComponents |= AttributesComponent; } @@ -420,7 +392,7 @@ class RenderController::SceneGraph // If attributes have changed, need to check if this has affected our motion sample times if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & TransformBlurGlobalComponent ) ) { - if( Private::RendererAlgo::transformMotionTimes( controller->m_motionBlurOptions.transformBlur, controller->m_motionBlurOptions.shutter, m_fullAttributes.get(), m_transformTimes ) ) + if( Private::RendererAlgo::transformMotionTimes( controller->m_renderOptions, m_fullAttributes.get(), m_transformTimes ) ) { m_dirtyComponents |= TransformComponent; } @@ -428,7 +400,7 @@ class RenderController::SceneGraph if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & DeformationBlurGlobalComponent ) ) { - if( Private::RendererAlgo::deformationMotionTimes( controller->m_motionBlurOptions.deformationBlur, controller->m_motionBlurOptions.shutter, m_fullAttributes.get(), m_deformationTimes ) ) + if( Private::RendererAlgo::deformationMotionTimes( controller->m_renderOptions, m_fullAttributes.get(), m_deformationTimes ) ) { m_dirtyComponents |= ObjectComponent; } @@ -461,7 +433,7 @@ class RenderController::SceneGraph if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & IncludedPurposesGlobalComponent ) ) { const bool purposeIncludedPreviously = m_purposeIncluded; - m_purposeIncluded = purposeIncluded( m_fullAttributes.get(), controller->m_globals.get() ); + m_purposeIncluded = controller->m_renderOptions.purposeIncluded( m_fullAttributes.get() ); if( m_purposeIncluded != purposeIncludedPreviously ) { // We'll need to hide or show the object by considering `m_purposeIncluded` in @@ -499,7 +471,7 @@ class RenderController::SceneGraph // Object - if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions.globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) { m_changedComponents |= ObjectComponent; } @@ -523,7 +495,7 @@ class RenderController::SceneGraph { // Failed to apply attributes - must replace entire object. m_objectHash = MurmurHash(); - if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions.globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) { m_changedComponents |= ObjectComponent; controller->m_failedAttributeEdits++; @@ -1348,8 +1320,7 @@ RenderController::RenderController( const ConstScenePlugPtr &scene, const Gaffer m_updateRequested( false ), m_failedAttributeEdits( 0 ), m_dirtyGlobalComponents( NoGlobalComponent ), - m_changedGlobalComponents( NoGlobalComponent ), - m_globals( new CompoundObject ) + m_changedGlobalComponents( NoGlobalComponent ) { for( int i = SceneGraph::FirstType; i <= SceneGraph::LastType; ++i ) { @@ -1628,50 +1599,46 @@ void RenderController::updateInternal( const ProgressCallback &callback, const I // Update globals if( m_dirtyGlobalComponents & GlobalsGlobalComponent ) { - ConstCompoundObjectPtr globals = m_scene->globalsPlug()->getValue(); - Private::RendererAlgo::outputOptions( globals.get(), m_globals.get(), m_renderer.get() ); - Private::RendererAlgo::outputOutputs( m_scene.get(), globals.get(), m_globals.get(), m_renderer.get() ); - if( !m_globals || *m_globals != *globals ) + RenderOptions renderOptions( m_scene.get() ); + Private::RendererAlgo::outputOptions( renderOptions.globals.get(), m_renderOptions.globals.get(), m_renderer.get() ); + Private::RendererAlgo::outputOutputs( m_scene.get(), renderOptions.globals.get(), m_renderOptions.globals.get(), m_renderer.get() ); + if( *renderOptions.globals != *m_renderOptions.globals ) { m_changedGlobalComponents |= GlobalsGlobalComponent; } - if( cameraGlobalsChanged( globals.get(), m_globals.get(), m_scene.get() ) ) + if( cameraGlobalsChanged( renderOptions.globals.get(), m_renderOptions.globals.get(), m_scene.get() ) ) { m_changedGlobalComponents |= CameraOptionsGlobalComponent; } - if( includedPurposesChanged( globals.get(), m_globals.get() ) ) + if( *renderOptions.includedPurposes != *m_renderOptions.includedPurposes ) { m_changedGlobalComponents |= IncludedPurposesGlobalComponent; } - m_globals = globals; - } - - // Update motion blur options - if( ( m_changedGlobalComponents & GlobalsGlobalComponent ) || ( m_dirtyGlobalComponents & CameraShutterGlobalComponent ) ) - { - const BoolData *transformBlurData = m_globals->member( g_transformBlurOptionName ); - bool transformBlur = transformBlurData ? transformBlurData->readable() : false; - - const BoolData *deformationBlurData = m_globals->member( g_deformationBlurOptionName ); - bool deformationBlur = deformationBlurData ? deformationBlurData->readable() : false; - - V2f shutter = SceneAlgo::shutter( m_globals.get(), m_scene.get() ); - - if( shutter != m_motionBlurOptions.shutter || transformBlur != m_motionBlurOptions.transformBlur ) + if( renderOptions.shutter != m_renderOptions.shutter || renderOptions.transformBlur != m_renderOptions.transformBlur ) { m_changedGlobalComponents |= TransformBlurGlobalComponent; } - if( shutter != m_motionBlurOptions.shutter || deformationBlur != m_motionBlurOptions.deformationBlur ) + if( renderOptions.shutter != m_renderOptions.shutter || renderOptions.deformationBlur != m_renderOptions.deformationBlur ) { m_changedGlobalComponents |= DeformationBlurGlobalComponent; } - if( shutter != m_motionBlurOptions.shutter ) + if( renderOptions.shutter != m_renderOptions.shutter ) { m_changedGlobalComponents |= CameraOptionsGlobalComponent; } - m_motionBlurOptions.transformBlur = transformBlur; - m_motionBlurOptions.deformationBlur = deformationBlur; - m_motionBlurOptions.shutter = shutter; + m_renderOptions = renderOptions; + } + + if( ( m_dirtyGlobalComponents & CameraShutterGlobalComponent ) && !( m_dirtyGlobalComponents & GlobalsGlobalComponent ) ) + { + // Shutter override from a camera may have changed, and won't have been covered by + // the block above (because the globals weren't dirty). + const V2f shutter = SceneAlgo::shutter( m_renderOptions.globals.get(), m_scene.get() ); + if( shutter != m_renderOptions.shutter ) + { + m_renderOptions.shutter = shutter; + m_changedGlobalComponents |= ( DeformationBlurGlobalComponent | TransformBlurGlobalComponent ); + } } if( m_dirtyGlobalComponents & SetsGlobalComponent ) @@ -1780,7 +1747,7 @@ void RenderController::updateDefaultCamera() return; } - const StringData *cameraOption = m_globals->member( g_cameraGlobalName ); + const StringData *cameraOption = m_renderOptions.globals->member( g_cameraGlobalName ); m_defaultCamera = nullptr; if( cameraOption && !cameraOption->readable().empty() ) { @@ -1788,7 +1755,7 @@ void RenderController::updateDefaultCamera() } CameraPtr defaultCamera = new IECoreScene::Camera; - SceneAlgo::applyCameraGlobals( defaultCamera.get(), m_globals.get(), m_scene.get() ); + SceneAlgo::applyCameraGlobals( defaultCamera.get(), m_renderOptions.globals.get(), m_scene.get() ); IECoreScenePreview::Renderer::AttributesInterfacePtr defaultAttributes = m_renderer->attributes( m_scene->attributesPlug()->defaultValue() ); ConstStringDataPtr name = new StringData( "gaffer:defaultCamera" ); m_defaultCamera = m_renderer->camera( name->readable(), defaultCamera.get(), defaultAttributes.get() ); diff --git a/src/GafferScene/RendererAlgo.cpp b/src/GafferScene/RendererAlgo.cpp index 7d46517a5f4..eec829cc0b2 100644 --- a/src/GafferScene/RendererAlgo.cpp +++ b/src/GafferScene/RendererAlgo.cpp @@ -77,6 +77,64 @@ using namespace IECoreScene; using namespace Gaffer; using namespace GafferScene; +////////////////////////////////////////////////////////////////////////// +// RenderOptions +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +const InternedString g_transformBlurOptionName( "option:render:transformBlur" ); +const InternedString g_deformationBlurOptionName( "option:render:deformationBlur" ); +const InternedString g_shutterOptionName( "option:render:shutter" ); +const InternedString g_includedPurposesOptionName( "option:render:includedPurposes" ); +const InternedString g_purposeAttributeName( "usd:purpose" ); + +/// \todo We should really default to `{ "default", "render" }`, but can only +/// change that on a major version update. +const ConstStringVectorDataPtr g_defaultIncludedPurposes( new StringVectorData( { "default", "render", "proxy", "guide" } ) ); +const std::string g_defaultPurpose( "default" ); + +} // namespace + +namespace GafferScene::Private::RendererAlgo +{ + +RenderOptions::RenderOptions() + : globals( new CompoundObject ), + transformBlur( false ), + deformationBlur( false ), + shutter( V2f( -0.25, 0.25 ) ), + includedPurposes( g_defaultIncludedPurposes ) +{ +} + +RenderOptions::RenderOptions( const ScenePlug *scene ) +{ + globals = scene->globals(); + + const BoolData *transformBlurData = globals->member( g_transformBlurOptionName ); + transformBlur = transformBlurData ? transformBlurData->readable() : false; + + const BoolData *deformationBlurData = globals->member( g_deformationBlurOptionName ); + deformationBlur = deformationBlurData ? deformationBlurData->readable() : false; + + shutter = SceneAlgo::shutter( globals.get(), scene ); + + const StringVectorData *includedPurposesData = globals->member( g_includedPurposesOptionName ); + includedPurposes = includedPurposesData ? includedPurposesData : g_defaultIncludedPurposes; +} + +bool RenderOptions::purposeIncluded( const CompoundObject *attributes ) const +{ + const auto purposeData = attributes->member( g_purposeAttributeName ); + const std::string &purpose = purposeData ? purposeData->readable() : g_defaultPurpose; + const vector &purposes = includedPurposes->readable(); + return std::find( purposes.begin(), purposes.end(), purpose ) != purposes.end(); +} + +} // namespace GafferScene::Private::RendererAlgo + ////////////////////////////////////////////////////////////////////////// // RendererAlgo implementation ////////////////////////////////////////////////////////////////////////// @@ -157,14 +215,14 @@ bool motionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *att return changed; } -bool transformMotionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *attributes, std::vector × ) +bool transformMotionTimes( const RenderOptions &renderOptions, const CompoundObject *attributes, std::vector × ) { - return motionTimes( motionBlur, shutter, attributes, g_transformBlurAttributeName, g_transformBlurSegmentsAttributeName, times ); + return motionTimes( renderOptions.transformBlur, renderOptions.shutter, attributes, g_transformBlurAttributeName, g_transformBlurSegmentsAttributeName, times ); } -bool deformationMotionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *attributes, std::vector × ) +bool deformationMotionTimes( const RenderOptions &renderOptions, const CompoundObject *attributes, std::vector × ) { - return motionTimes( motionBlur, shutter, attributes, g_deformationBlurAttributeName, g_deformationBlurSegmentsAttributeName, times ); + return motionTimes( renderOptions.deformationBlur, renderOptions.shutter, attributes, g_deformationBlurAttributeName, g_deformationBlurSegmentsAttributeName, times ); } bool transformSamples( const M44fPlug *transformPlug, const std::vector &sampleTimes, std::vector &samples, IECore::MurmurHash *hash ) @@ -1009,15 +1067,9 @@ namespace { const std::string g_optionPrefix( "option:" ); -const std::string g_defaultPurpose( "default" ); const IECore::InternedString g_frameOptionName( "frame" ); const IECore::InternedString g_cameraOptionLegacyName( "option:render:camera" ); -const InternedString g_transformBlurOptionName( "option:render:transformBlur" ); -const InternedString g_deformationBlurOptionName( "option:render:deformationBlur" ); -const InternedString g_shutterOptionName( "option:render:shutter" ); -const InternedString g_includedPurposesOptionName( "option:render:includedPurposes" ); -const InternedString g_purposeAttributeName( "usd:purpose" ); InternedString g_visibleAttributeName( "scene:visible" ); @@ -1036,20 +1088,9 @@ IECore::InternedString optionName( const IECore::InternedString &globalsName ) struct LocationOutput { - LocationOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene ) - : m_renderer( renderer ), m_attributes( root.empty() ? SceneAlgo::globalAttributes( globals ) : new CompoundObject ), - m_renderSets( renderSets ), m_root( root ) + LocationOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + : m_renderer( renderer ), m_options( renderOptions ), m_attributes( root.empty() ? SceneAlgo::globalAttributes( renderOptions.globals.get() ) : new CompoundObject ), m_renderSets( renderSets ), m_root( root ) { - const BoolData *transformBlurData = globals->member( g_transformBlurOptionName ); - m_options.transformBlur = transformBlurData ? transformBlurData->readable() : false; - - const BoolData *deformationBlurData = globals->member( g_deformationBlurOptionName ); - m_options.deformationBlur = deformationBlurData ? deformationBlurData->readable() : false; - - m_options.shutter = SceneAlgo::shutter( globals, scene ); - - m_options.includedPurposes = globals->member( g_includedPurposesOptionName ); - m_transformSamples.push_back( M44f() ); } @@ -1080,15 +1121,7 @@ struct LocationOutput bool purposeIncluded() const { - if( !m_options.includedPurposes ) - { - return true; - } - const auto purposeData = m_attributes->member( g_purposeAttributeName ); - const std::string &purpose = purposeData ? purposeData->readable() : g_defaultPurpose; - const vector &purposes = m_options.includedPurposes->readable(); - return std::find( purposes.begin(), purposes.end(), purpose ) != purposes.end(); - + return m_options.purposeIncluded( m_attributes.get() ); } std::string name( const ScenePlug::ScenePath &path ) const @@ -1115,7 +1148,7 @@ struct LocationOutput void deformationMotionTimes( std::vector × ) { - GafferScene::Private::RendererAlgo::deformationMotionTimes( m_options.deformationBlur, m_options.shutter, m_attributes.get(), times ); + GafferScene::Private::RendererAlgo::deformationMotionTimes( m_options, m_attributes.get(), times ); } const IECore::CompoundObject *attributes() const @@ -1180,7 +1213,7 @@ struct LocationOutput void updateTransform( const ScenePlug *scene ) { vector sampleTimes; - GafferScene::Private::RendererAlgo::transformMotionTimes( m_options.transformBlur, m_options.shutter, m_attributes.get(), sampleTimes ); + GafferScene::Private::RendererAlgo::transformMotionTimes( m_options, m_attributes.get(), sampleTimes ); vector samples; GafferScene::Private::RendererAlgo::transformSamples( scene->transformPlug(), sampleTimes, samples ); @@ -1241,15 +1274,7 @@ struct LocationOutput IECoreScenePreview::Renderer *m_renderer; - struct Options - { - bool transformBlur; - bool deformationBlur; - Imath::V2f shutter; - ConstStringVectorDataPtr includedPurposes; - }; - - Options m_options; + const GafferScene::Private::RendererAlgo::RenderOptions m_options; IECore::ConstCompoundObjectPtr m_attributes; const GafferScene::Private::RendererAlgo::RenderSets &m_renderSets; const ScenePlug::ScenePath &m_root; @@ -1262,8 +1287,8 @@ struct LocationOutput struct CameraOutput : public LocationOutput { - CameraOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene ) - : LocationOutput( renderer, globals, renderSets, root, scene ), m_globals( globals ), m_cameraSet( renderSets.camerasSet() ) + CameraOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_globals( renderOptions.globals.get() ), m_cameraSet( renderSets.camerasSet() ) { } @@ -1354,8 +1379,8 @@ struct CameraOutput : public LocationOutput struct LightOutput : public LocationOutput { - LightOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) - : LocationOutput( renderer, globals, renderSets, root, scene ), m_lightSet( renderSets.lightsSet() ), m_lightLinks( lightLinks ) + LightOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_lightSet( renderSets.lightsSet() ), m_lightLinks( lightLinks ) { } @@ -1400,8 +1425,8 @@ struct LightOutput : public LocationOutput struct LightFiltersOutput : public LocationOutput { - LightFiltersOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) - : LocationOutput( renderer, globals, renderSets, root, scene ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks ) + LightFiltersOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks ) { } @@ -1446,8 +1471,8 @@ struct LightFiltersOutput : public LocationOutput struct ObjectOutput : public LocationOutput { - ObjectOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) - : LocationOutput( renderer, globals, renderSets, root, scene ), m_cameraSet( renderSets.camerasSet() ), m_lightSet( renderSets.lightsSet() ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks ) + ObjectOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_cameraSet( renderSets.camerasSet() ), m_lightSet( renderSets.lightsSet() ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks ) { } @@ -1709,9 +1734,9 @@ void outputOutputs( const ScenePlug *scene, const IECore::CompoundObject *global } } -void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer ) +void outputCameras( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer ) { - const StringData *cameraOption = globals->member( g_cameraOptionLegacyName ); + const StringData *cameraOption = renderOptions.globals->member( g_cameraOptionLegacyName ); if( cameraOption && !cameraOption->readable().empty() ) { ScenePlug::ScenePath cameraPath; ScenePlug::stringToPath( cameraOption->readable(), cameraPath ); @@ -1730,13 +1755,13 @@ void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *global } const ScenePlug::ScenePath root; - CameraOutput output( renderer, globals, renderSets, root, scene ); + CameraOutput output( renderer, renderOptions, renderSets, root, scene ); SceneAlgo::parallelProcessLocations( scene, output ); if( !cameraOption || cameraOption->readable().empty() ) { CameraPtr defaultCamera = new IECoreScene::Camera; - SceneAlgo::applyCameraGlobals( defaultCamera.get(), globals, scene ); + SceneAlgo::applyCameraGlobals( defaultCamera.get(), renderOptions.globals.get(), scene ); IECoreScenePreview::Renderer::AttributesInterfacePtr defaultAttributes = renderer->attributes( scene->attributesPlug()->defaultValue() ); ConstStringDataPtr name = new StringData( "gaffer:defaultCamera" ); renderer->camera( name->readable(), defaultCamera.get(), defaultAttributes.get() ); @@ -1744,23 +1769,23 @@ void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *global } } -void outputLightFilters( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ) +void outputLightFilters( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ) { const ScenePlug::ScenePath root; - LightFiltersOutput output( renderer, globals, renderSets, lightLinks, root, scene ); + LightFiltersOutput output( renderer, renderOptions, renderSets, lightLinks, root, scene ); SceneAlgo::parallelProcessLocations( scene, output ); } -void outputLights( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ) +void outputLights( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ) { const ScenePlug::ScenePath root; - LightOutput output( renderer, globals, renderSets, lightLinks, root, scene ); + LightOutput output( renderer, renderOptions, renderSets, lightLinks, root, scene ); SceneAlgo::parallelProcessLocations( scene, output ); } -void outputObjects( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root ) +void outputObjects( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root ) { - ObjectOutput output( renderer, globals, renderSets, lightLinks, root, scene ); + ObjectOutput output( renderer, renderOptions, renderSets, lightLinks, root, scene ); SceneAlgo::parallelProcessLocations( scene, output, root ); } diff --git a/src/GafferSceneModule/RenderBinding.cpp b/src/GafferSceneModule/RenderBinding.cpp index c74207961c3..52c7eefd13d 100644 --- a/src/GafferSceneModule/RenderBinding.cpp +++ b/src/GafferSceneModule/RenderBinding.cpp @@ -389,22 +389,22 @@ object transformSamplesWrapper( const Gaffer::M44fPlug &transformPlug, const std return pythonSamples; } -void outputCamerasWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, IECoreScenePreview::Renderer &renderer ) +void outputCamerasWrapper( const ScenePlug &scene, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, IECoreScenePreview::Renderer &renderer ) { IECorePython::ScopedGILRelease gilRelease; - GafferScene::Private::RendererAlgo::outputCameras( &scene, &globals, renderSets, &renderer ); + GafferScene::Private::RendererAlgo::outputCameras( &scene, renderOptions, renderSets, &renderer ); } -void outputLightsWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer ) +void outputLightsWrapper( const ScenePlug &scene, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer ) { IECorePython::ScopedGILRelease gilRelease; - GafferScene::Private::RendererAlgo::outputLights( &scene, &globals, renderSets, &lightLinks, &renderer ); + GafferScene::Private::RendererAlgo::outputLights( &scene, renderOptions, renderSets, &lightLinks, &renderer ); } -void outputObjectsWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer, const ScenePlug::ScenePath &root ) +void outputObjectsWrapper( const ScenePlug &scene, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer, const ScenePlug::ScenePath &root ) { IECorePython::ScopedGILRelease gilRelease; - GafferScene::Private::RendererAlgo::outputObjects( &scene, &globals, renderSets, &lightLinks, &renderer, root ); + GafferScene::Private::RendererAlgo::outputObjects( &scene, renderOptions, renderSets, &lightLinks, &renderer, root ); } } // namespace @@ -444,6 +444,15 @@ void GafferSceneModule::bindRender() scope rendererAlgomoduleScope( rendererAlgoModule ); + class_( "RenderOptions" ) + .def( init() ) + .def_readwrite( "globals", &GafferScene::Private::RendererAlgo::RenderOptions::globals ) + .def_readwrite( "transformBlur", &GafferScene::Private::RendererAlgo::RenderOptions::transformBlur ) + .def_readwrite( "deformationBlur", &GafferScene::Private::RendererAlgo::RenderOptions::deformationBlur ) + .def_readwrite( "shutter", &GafferScene::Private::RendererAlgo::RenderOptions::shutter ) + .def_readwrite( "includedPurposes", &GafferScene::Private::RendererAlgo::RenderOptions::includedPurposes ) + ; + def( "objectSamples", &objectSamplesWrapper, ( arg( "objectPlug" ), arg( "sampleTimes" ), arg( "hash" ) = object(), arg( "_copy" ) = true ) ); def( "transformSamples", &transformSamplesWrapper, ( arg( "transformPlug" ), arg( "sampleTimes" ), arg( "hash" ) = object() ) ); From d2ba47e9e68cf3ad3e81d1d304df158eb22e5606 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 20 Sep 2023 15:13:00 +0100 Subject: [PATCH 6/8] RendererAlgo : Fix render options used for Capsules --- include/GafferScene/Capsule.h | 7 + python/GafferSceneTest/RendererAlgoTest.py | 176 +++++++++++++++++++++ python/GafferSceneUI/EncapsulateUI.py | 8 +- src/GafferScene/Capsule.cpp | 31 +++- src/GafferScene/RendererAlgo.cpp | 18 ++- src/GafferSceneModule/HierarchyBinding.cpp | 11 ++ 6 files changed, 243 insertions(+), 8 deletions(-) diff --git a/include/GafferScene/Capsule.h b/include/GafferScene/Capsule.h index 1cb625afbf5..fefc6913345 100644 --- a/include/GafferScene/Capsule.h +++ b/include/GafferScene/Capsule.h @@ -37,6 +37,7 @@ #pragma once #include "GafferScene/Private/IECoreScenePreview/Procedural.h" +#include "GafferScene/Private/RendererAlgo.h" #include "GafferScene/ScenePlug.h" #include "Gaffer/Context.h" @@ -90,6 +91,11 @@ class GAFFERSCENE_API Capsule : public IECoreScenePreview::Procedural const ScenePlug::ScenePath &root() const; const Gaffer::Context *context() const; + /// Used to apply the correct render settings to the capsule before rendering it. + /// For internal use only. + void setRenderOptions( const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions ); + std::optional getRenderOptions() const; + private : void throwIfNoScene() const; @@ -106,6 +112,7 @@ class GAFFERSCENE_API Capsule : public IECoreScenePreview::Procedural const ScenePlug *m_scene; ScenePlug::ScenePath m_root; Gaffer::ConstContextPtr m_context; + std::optional m_renderOptions; }; diff --git a/python/GafferSceneTest/RendererAlgoTest.py b/python/GafferSceneTest/RendererAlgoTest.py index 9829c90af34..6f7e799177e 100644 --- a/python/GafferSceneTest/RendererAlgoTest.py +++ b/python/GafferSceneTest/RendererAlgoTest.py @@ -36,6 +36,8 @@ import unittest +import imath + import IECore import Gaffer @@ -749,5 +751,179 @@ def assertIncludedObjects( scene, includedPurposes, paths ) : } ) + def testCapsuleMotionBlur( self ) : + + sphere = GafferScene.Sphere() + sphere["type"].setValue( sphere.Type.Primitive ) + sphere["expression"] = Gaffer.Expression() + sphere["expression"].setExpression( + 'parent["radius"] = context.getFrame() + 1; parent["transform"]["translate"]["x"] = context.getFrame()' + ) + + group = GafferScene.Group() + group["in"][0].setInput( sphere["out"] ) + + groupFilter = GafferScene.PathFilter() + groupFilter["paths"].setValue( IECore.StringVectorData( [ "/group" ] ) ) + + encapsulate = GafferScene.Encapsulate() + encapsulate["in"].setInput( group["out"] ) + encapsulate["filter"].setInput( groupFilter["out"] ) + + camera = GafferScene.Camera() + camera["renderSettingOverrides"]["shutter"]["value"].setValue( imath.V2f( -0.5, 0.5 ) ) + + parent = GafferScene.Parent() + parent["in"].setInput( encapsulate["out"] ) + parent["parent"].setValue( "/" ) + parent["in"].setInput( encapsulate["out"] ) + parent["children"][0].setInput( camera["out"] ) + + standardOptions = GafferScene.StandardOptions() + standardOptions["in"].setInput( parent["out"] ) + standardOptions["options"]["transformBlur"]["enabled"].setValue( True ) + standardOptions["options"]["deformationBlur"]["enabled"].setValue( True ) + standardOptions["options"]["shutter"]["enabled"].setValue( True ) + standardOptions["options"]["renderCamera"]["enabled"].setValue( True ) + standardOptions["options"]["renderCamera"]["value"].setValue( "/camera" ) + + def assertExpectedMotion( scene ) : + + # Render to capture Capsule. This will always contain only a single + # motion sample because procedurals themselves can't have motion samples + # (although their contents can). + + renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch + ) + GafferScene.Private.RendererAlgo.outputObjects( + scene, GafferScene.Private.RendererAlgo.RenderOptions( scene ), + GafferScene.Private.RendererAlgo.RenderSets( scene ), GafferScene.Private.RendererAlgo.LightLinks(), + renderer + ) + + capsule = renderer.capturedObject( "/group" ) + self.assertEqual( len( capsule.capturedSamples() ), 1 ) + capsule = capsule.capturedSamples()[0] + self.assertIsInstance( capsule, GafferScene.Capsule ) + + # Render again to expand contents of Capsule. + + capsuleRenderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch + ) + capsule.render( capsuleRenderer ) + + # Check transform blur of contents matches what was requested by the scene globals. + + motionTimes = list( GafferScene.SceneAlgo.shutter( scene.globals(), scene ) ) + sphere = capsuleRenderer.capturedObject( "/sphere" ) + if scene.globals()["option:render:transformBlur"].value : + transformTimes = motionTimes + else : + transformTimes = [ Gaffer.Context.current().getFrame() ] + + self.assertEqual( len( sphere.capturedTransforms() ), len( transformTimes ) ) + self.assertEqual( sphere.capturedTransformTimes(), transformTimes if len( transformTimes ) > 1 else [] ) + for index, time in enumerate( transformTimes ) : + with Gaffer.Context() as context : + context.setFrame( time ) + self.assertEqual( sphere.capturedTransforms()[index], capsule.scene().transform( "/group/sphere" ) ) + + # Check deformation blur of contents matches what was requested by the scene globals. + + if scene.globals()["option:render:deformationBlur"].value : + objectTimes = motionTimes + else : + objectTimes = [ Gaffer.Context.current().getFrame() ] + + self.assertEqual( len( sphere.capturedSamples() ), len( objectTimes ) ) + self.assertEqual( sphere.capturedSampleTimes(), objectTimes if len( objectTimes ) > 1 else [] ) + for index, time in enumerate( objectTimes ) : + with Gaffer.Context() as context : + context.setFrame( time ) + self.assertEqual( sphere.capturedSamples()[index].radius, capsule.scene().object( "/group/sphere" ).radius ) + + for frame in ( 0, 1 ) : + for deformation in ( False, True ) : + for transform in ( False, True ) : + for shutter in ( imath.V2f( -0.25, 0.25 ), imath.V2f( 0, 0.5 ) ) : + for overrideShutter in ( False, True ) : + with self.subTest( frame = frame, deformation = deformation, transform = transform, shutter = shutter, overrideShutter = overrideShutter ) : + standardOptions["options"]["transformBlur"]["value"].setValue( transform ) + standardOptions["options"]["deformationBlur"]["value"].setValue( deformation ) + standardOptions["options"]["shutter"]["value"].setValue( shutter ) + camera["renderSettingOverrides"]["shutter"]["enabled"].setValue( overrideShutter ) + with Gaffer.Context() as context : + context.setFrame( frame ) + assertExpectedMotion( standardOptions["out"] ) + + def testCapsulePurposes( self ) : + + rootFilter = GafferScene.PathFilter() + rootFilter["paths"].setValue( IECore.StringVectorData( [ "*" ] ) ) + + cube = GafferScene.Cube() + + attributes = GafferScene.CustomAttributes() + attributes["in"].setInput( cube["out"] ) + attributes["filter"].setInput( rootFilter["out"] ) + attributes["attributes"].addChild( Gaffer.NameValuePlug( "usd:purpose", "${collect:rootName}" ) ) + + collect = GafferScene.CollectScenes() + collect["in"].setInput( attributes["out"] ) + collect["rootNames"].setValue( IECore.StringVectorData( [ "default", "render", "proxy", "guide" ] ) ) + + group = GafferScene.Group() + group["in"][0].setInput( collect["out"] ) + + encapsulate = GafferScene.Encapsulate() + encapsulate["in"].setInput( group["out"] ) + encapsulate["filter"].setInput( rootFilter["out"] ) + + standardOptions = GafferScene.StandardOptions() + standardOptions["in"].setInput( encapsulate["out"] ) + standardOptions["options"]["includedPurposes"]["enabled"].setValue( True ) + + for includedPurposes in [ + [ "default", "render" ], + [ "default", "proxy" ], + [ "default", "render", "proxy", "guide" ], + [ "default" ], + ] : + with self.subTest( includedPurposes = includedPurposes ) : + + standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( includedPurposes ) ) + + # Render to capture Capsule. + + renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch + ) + GafferScene.Private.RendererAlgo.outputObjects( + standardOptions["out"], GafferScene.Private.RendererAlgo.RenderOptions( standardOptions["out"] ), + GafferScene.Private.RendererAlgo.RenderSets( standardOptions["out"] ), GafferScene.Private.RendererAlgo.LightLinks(), + renderer + ) + + capsule = renderer.capturedObject( "/group" ) + capsule = capsule.capturedSamples()[0] + self.assertIsInstance( capsule, GafferScene.Capsule ) + + # Render again to expand contents of Capsule. + + capsuleRenderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer( + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch + ) + capsule.render( capsuleRenderer ) + + # Check that only objects with the right purpose have been included. + + for purpose in [ "default", "render", "proxy", "guide" ] : + if purpose in includedPurposes : + self.assertIsNotNone( capsuleRenderer.capturedObject( f"/{purpose}/cube" ) ) + else : + self.assertIsNone( capsuleRenderer.capturedObject( f"/{purpose}/cube" ) ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneUI/EncapsulateUI.py b/python/GafferSceneUI/EncapsulateUI.py index a180c6eca86..17669037b12 100644 --- a/python/GafferSceneUI/EncapsulateUI.py +++ b/python/GafferSceneUI/EncapsulateUI.py @@ -61,11 +61,11 @@ > Note : Encapsulation currently has some limitations > - > - Motion blur options are taken from the globals at the - > point of Encapsulation, not the downstream globals - > at the point of rendering. > - Motion blur attributes are not inherited - only - > attributes within the encapsulate hierarchy are + > attributes within the encapsulated hierarchy are + > considered. + > - The `usd:purpose` attribute is not inherited - only + > attributes within the encapsulated hierarchy are > considered. """, diff --git a/src/GafferScene/Capsule.cpp b/src/GafferScene/Capsule.cpp index dfc8b9133ee..76764d02791 100644 --- a/src/GafferScene/Capsule.cpp +++ b/src/GafferScene/Capsule.cpp @@ -103,6 +103,16 @@ void Capsule::hash( IECore::MurmurHash &h ) const Procedural::hash( h ); h.append( m_hash ); + + if( m_renderOptions ) + { + // Hash only what affects our rendering, not everything in + // `RenderOptions::globals`. + h.append( m_renderOptions->transformBlur ); + h.append( m_renderOptions->deformationBlur ); + h.append( m_renderOptions->shutter ); + m_renderOptions->includedPurposes->hash( h ); + } } void Capsule::copyFrom( const IECore::Object *other, IECore::Object::CopyContext *context ) @@ -147,9 +157,13 @@ void Capsule::render( IECoreScenePreview::Renderer *renderer ) const { throwIfNoScene(); ScenePlug::GlobalScope scope( m_context.get() ); - GafferScene::Private::RendererAlgo::RenderOptions renderOptions( m_scene ); + std::optional renderOptions = m_renderOptions; + if( !renderOptions ) + { + renderOptions = GafferScene::Private::RendererAlgo::RenderOptions( m_scene ); + } GafferScene::Private::RendererAlgo::RenderSets renderSets( m_scene ); - GafferScene::Private::RendererAlgo::outputObjects( m_scene, renderOptions, renderSets, /* lightLinks = */ nullptr, renderer, m_root ); + GafferScene::Private::RendererAlgo::outputObjects( m_scene, *renderOptions, renderSets, /* lightLinks = */ nullptr, renderer, m_root ); } const ScenePlug *Capsule::scene() const @@ -170,6 +184,19 @@ const Gaffer::Context *Capsule::context() const return m_context.get(); } +void Capsule::setRenderOptions( const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions ) +{ + // This is not pretty, but it allows the capsule to render with the correct + // motion blur and `includedPurposes`, taken from the downstream node being + // rendered rather than from the capsule's own globals. + m_renderOptions = renderOptions; +} + +std::optional Capsule::getRenderOptions() const +{ + return m_renderOptions; +} + void Capsule::throwIfNoScene() const { if( !m_scene ) diff --git a/src/GafferScene/RendererAlgo.cpp b/src/GafferScene/RendererAlgo.cpp index eec829cc0b2..5f9b7549db4 100644 --- a/src/GafferScene/RendererAlgo.cpp +++ b/src/GafferScene/RendererAlgo.cpp @@ -36,6 +36,7 @@ #include "GafferScene/Private/RendererAlgo.h" +#include "GafferScene/Capsule.h" #include "GafferScene/Private/IECoreScenePreview/Renderer.h" #include "GafferScene/SceneAlgo.h" #include "GafferScene/SceneProcessor.h" @@ -1119,6 +1120,11 @@ struct LocationOutput protected : + const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions() const + { + return m_options; + } + bool purposeIncluded() const { return m_options.purposeIncluded( m_attributes.get() ); @@ -1505,12 +1511,20 @@ struct ObjectOutput : public LocationOutput IECoreScenePreview::Renderer::ObjectInterfacePtr objectInterface; IECoreScenePreview::Renderer::AttributesInterfacePtr attributesInterface = this->attributesInterface(); - if( !sampleTimes.size() ) + if( samples.size() == 1 ) { - objectInterface = renderer()->object( name( path ), samples[0].get(), attributesInterface.get() ); + ConstObjectPtr sample = samples[0]; + if( auto capsule = runTimeCast( sample.get() ) ) + { + CapsulePtr capsuleCopy = capsule->copy(); + capsuleCopy->setRenderOptions( renderOptions() ); + sample = capsuleCopy; + } + objectInterface = renderer()->object( name( path ), sample.get(), attributesInterface.get() ); } else { + assert( sampleTimes.size() == samples.size() ); /// \todo Can we rejig things so this conversion isn't necessary? vector objectsVector; objectsVector.reserve( samples.size() ); for( const auto &sample : samples ) diff --git a/src/GafferSceneModule/HierarchyBinding.cpp b/src/GafferSceneModule/HierarchyBinding.cpp index f0c18077292..9b7a51284ff 100644 --- a/src/GafferSceneModule/HierarchyBinding.cpp +++ b/src/GafferSceneModule/HierarchyBinding.cpp @@ -67,6 +67,15 @@ using namespace GafferScene; namespace { +object getRenderOptionsWrapper( const Capsule &c ) +{ + if( auto o = c.getRenderOptions() ) + { + return object( *o ); + } + return object(); +} + ScenePlugPtr scene( const Capsule &c ) { return const_cast( c.scene() ); @@ -102,6 +111,8 @@ void GafferSceneModule::bindHierarchy() .def( "scene", &scene ) .def( "root", &root ) .def( "context", &context ) + .def( "setRenderOptions", &Capsule::setRenderOptions ) + .def( "getRenderOptions", &getRenderOptionsWrapper ) ; GafferBindings::DependencyNodeClass() From 3c84f70361ac59b51fb78f4ff204dc31d6ea0558 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Mon, 25 Sep 2023 13:59:00 +0100 Subject: [PATCH 7/8] RenderController : Pass RenderOptions changes to Capsules --- Changes.md | 4 +- include/GafferScene/Private/RendererAlgo.h | 1 + include/GafferScene/RenderController.h | 1 + .../GafferSceneTest/RenderControllerTest.py | 99 +++++++++++++++++++ src/GafferScene/RenderController.cpp | 39 ++++++-- src/GafferScene/RendererAlgo.cpp | 7 ++ src/GafferSceneModule/RenderBinding.cpp | 1 + 7 files changed, 145 insertions(+), 7 deletions(-) diff --git a/Changes.md b/Changes.md index 4e40cbe6a27..f1815ea6369 100644 --- a/Changes.md +++ b/Changes.md @@ -17,7 +17,9 @@ Fixes - StringPlugValueWidget : Fixed bug handling Esc. - Arnold : Fixed unnecessary `opaque` attribute deprecation warnings. These are now only emitted in the case that `opaque` has been explicitly turned off. - ShaderUI : Fixed bug causing identical but independent shaders in a shader network from being included in the shader browser. -- Encapsulate : Fixed bug where global attributes (from the point of encapsulation) were baked into the contents of the capsule instead of being inherited naturally (from the node being rendered). +- Encapsulate : + - Fixed bug where global attributes (from the point of encapsulation) were baked into the contents of the capsule instead of being inherited naturally (from the node being rendered). + - Fixed motion blur so that global settings are now taken from the downstream node being rendered, not from the input to the Encapsulate node. API --- diff --git a/include/GafferScene/Private/RendererAlgo.h b/include/GafferScene/Private/RendererAlgo.h index 34e9697c26b..11f7bf81baf 100644 --- a/include/GafferScene/Private/RendererAlgo.h +++ b/include/GafferScene/Private/RendererAlgo.h @@ -69,6 +69,7 @@ struct GAFFERSCENE_API RenderOptions RenderOptions( const ScenePlug *scene ); RenderOptions( const RenderOptions &other ) = default; RenderOptions& operator=( const RenderOptions &other ) = default; + bool operator==( const RenderOptions &other ) const; /// The globals from the scene. IECore::ConstCompoundObjectPtr globals; /// Convenient access to specific properties, taking into account default diff --git a/include/GafferScene/RenderController.h b/include/GafferScene/RenderController.h index 66b23656a3f..dc8e1cad1bb 100644 --- a/include/GafferScene/RenderController.h +++ b/include/GafferScene/RenderController.h @@ -122,6 +122,7 @@ class GAFFERSCENE_API RenderController : public Gaffer::Signals::Trackable DeformationBlurGlobalComponent = 32, CameraShutterGlobalComponent = 64, IncludedPurposesGlobalComponent = 128, + CapsuleAffectingGlobalComponents = TransformBlurGlobalComponent | DeformationBlurGlobalComponent | IncludedPurposesGlobalComponent, AllGlobalComponents = GlobalsGlobalComponent | SetsGlobalComponent | RenderSetsGlobalComponent | CameraOptionsGlobalComponent | TransformBlurGlobalComponent | DeformationBlurGlobalComponent | IncludedPurposesGlobalComponent }; diff --git a/python/GafferSceneTest/RenderControllerTest.py b/python/GafferSceneTest/RenderControllerTest.py index 9cdf17d963b..55e8329c512 100644 --- a/python/GafferSceneTest/RenderControllerTest.py +++ b/python/GafferSceneTest/RenderControllerTest.py @@ -1584,5 +1584,104 @@ def testIncludedPurposes( self ) : controller.update() self.assertIsNotNone( renderer.capturedObject( "/group/sphere" ) ) + def testCapsuleRenderOptions( self ) : + + rootFilter = GafferScene.PathFilter() + rootFilter["paths"].setValue( IECore.StringVectorData( [ "*" ] ) ) + + cube = GafferScene.Cube() + + encapsulate = GafferScene.Encapsulate() + encapsulate["in"].setInput( cube["out"] ) + encapsulate["filter"].setInput( rootFilter["out"] ) + + standardOptions = GafferScene.StandardOptions() + standardOptions["in"].setInput( encapsulate["out"] ) + standardOptions["options"]["includedPurposes"]["enabled"].setValue( True ) + + renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer() + controller = GafferScene.RenderController( standardOptions["out"], Gaffer.Context(), renderer ) + controller.setMinimumExpansionDepth( 2 ) + + def assertExpectedRenderOptions() : + + captured = renderer.capturedObject( "/cube" ) + self.assertIsNotNone( captured ) + self.assertEqual( len( captured.capturedSamples() ), 1 ) + self.assertIsInstance( captured.capturedSamples()[0], GafferScene.Capsule ) + self.assertEqual( + captured.capturedSamples()[0].getRenderOptions(), + GafferScene.Private.RendererAlgo.RenderOptions( standardOptions["out"] ) + ) + + # Check that a capsule has the initial RenderOptions we expect. + + self.assertTrue( controller.updateRequired() ) + controller.update() + assertExpectedRenderOptions() + + # Check that the capsule is updated when the RenderOptions change. + + standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( [ "default", "proxy" ] ) ) + self.assertTrue( controller.updateRequired() ) + controller.update() + assertExpectedRenderOptions() + + # Check that the capsule is not updated when the globals change + # but the RenderOptions that the capsule uses aren't affected. + + capture = renderer.capturedObject( "/cube" ) + standardOptions["options"]["performanceMonitor"]["enabled"].setValue( True ) + self.assertTrue( controller.updateRequired() ) + controller.update() + self.assertTrue( renderer.capturedObject( "/cube" ).isSame( capture ) ) + + # Change RenderOptions again, this time to the default, and check we + # get another update. + + del capture + standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( [ "default", "render", "proxy", "guide" ] ) ) + self.assertTrue( controller.updateRequired() ) + controller.update() + assertExpectedRenderOptions() + + # Remove `includedPurposes` option, so it's not in the globals. The + # fallback is the same as the previous value, so we should get no + # update. + + capture = renderer.capturedObject( "/cube" ) + standardOptions["options"]["includedPurposes"]["enabled"].setValue( False ) + self.assertTrue( controller.updateRequired() ) + controller.update() + self.assertTrue( renderer.capturedObject( "/cube" ).isSame( capture ) ) + + def testNoUnnecessaryObjectUpdatesOnPurposeChange( self ) : + + cube = GafferScene.Cube() + + standardOptions = GafferScene.StandardOptions() + standardOptions["in"].setInput( cube["out"] ) + + renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer() + controller = GafferScene.RenderController( standardOptions["out"], Gaffer.Context(), renderer ) + controller.setMinimumExpansionDepth( 2 ) + + # Check initial capture + + self.assertTrue( controller.updateRequired() ) + controller.update() + capture = renderer.capturedObject( "/cube" ) + self.assertIsNotNone( capture ) + + # Check that changing the purposes doesn't make an unnecessary edit for + # the object. It was included before and it is still included, so we + # want to reuse the old object. + + standardOptions["options"]["includedPurposes"]["enabled"].setValue( True ) + standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( [ "default", "proxy" ] ) ) + self.assertTrue( controller.updateRequired() ) + controller.update() + self.assertTrue( capture.isSame( renderer.capturedObject( "/cube" ) ) ) + if __name__ == "__main__": unittest.main() diff --git a/src/GafferScene/RenderController.cpp b/src/GafferScene/RenderController.cpp index 5fa551b6849..f5a32cba0bb 100644 --- a/src/GafferScene/RenderController.cpp +++ b/src/GafferScene/RenderController.cpp @@ -36,6 +36,7 @@ #include "GafferScene/RenderController.h" +#include "GafferScene/Capsule.h" #include "GafferScene/Private/IECoreScenePreview/Placeholder.h" #include "GafferScene/SceneAlgo.h" @@ -173,6 +174,7 @@ struct ObjectInterfaceHandle : public boost::noncopyable using RemovalCallback = std::function; ObjectInterfaceHandle() + : m_isCapsule( false ) { } @@ -189,7 +191,7 @@ struct ObjectInterfaceHandle : public boost::noncopyable assign( p, RemovalCallback() ); } - void assign( const IECoreScenePreview::Renderer::ObjectInterfacePtr &p, const RemovalCallback &removalCallback ) + void assign( const IECoreScenePreview::Renderer::ObjectInterfacePtr &p, const RemovalCallback &removalCallback, bool isCapsule = false ) { if( m_removalCallback ) { @@ -197,6 +199,7 @@ struct ObjectInterfaceHandle : public boost::noncopyable } m_objectInterface = p; m_removalCallback = removalCallback; + m_isCapsule = isCapsule; } IECoreScenePreview::Renderer::ObjectInterface *operator->() const @@ -214,10 +217,16 @@ struct ObjectInterfaceHandle : public boost::noncopyable return m_objectInterface.get(); } + bool isCapsule() const + { + return m_isCapsule; + } + private : IECoreScenePreview::Renderer::ObjectInterfacePtr m_objectInterface; RemovalCallback m_removalCallback; + bool m_isCapsule; }; @@ -471,7 +480,14 @@ class RenderController::SceneGraph // Object - if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions.globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( m_objectInterface.isCapsule() && ( changedGlobals & CapsuleAffectingGlobalComponents ) ) + { + // Account for `Capsule::setRenderOptions()` being called in `updateObject()`. + m_dirtyComponents |= ObjectComponent; + m_objectHash = MurmurHash(); + } + + if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) ) { m_changedComponents |= ObjectComponent; } @@ -495,7 +511,7 @@ class RenderController::SceneGraph { // Failed to apply attributes - must replace entire object. m_objectHash = MurmurHash(); - if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions.globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) ) { m_changedComponents |= ObjectComponent; controller->m_failedAttributeEdits++; @@ -787,7 +803,7 @@ class RenderController::SceneGraph } // Returns true if the object changed. - bool updateObject( const ObjectPlug *objectPlug, Type type, IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const ScenePlug *scene, LightLinks *lightLinks ) + bool updateObject( const ObjectPlug *objectPlug, Type type, IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const ScenePlug *scene, LightLinks *lightLinks ) { const bool hadObjectInterface = static_cast( m_objectInterface ); if( type == NoType || m_drawMode != VisibleSet::Visibility::Visible || !m_purposeIncluded ) @@ -905,7 +921,7 @@ class RenderController::SceneGraph if( auto cameraSample = runTimeCast( sample.get() ) ) { IECoreScene::CameraPtr cameraSampleCopy = cameraSample->copy(); - SceneAlgo::applyCameraGlobals( cameraSampleCopy.get(), globals, scene ); + SceneAlgo::applyCameraGlobals( cameraSampleCopy.get(), renderOptions.globals.get(), scene ); cameraSamples.push_back( cameraSampleCopy ); } } @@ -958,7 +974,18 @@ class RenderController::SceneGraph if( samples.size() == 1 ) { - m_objectInterface = renderer->object( name, samples[0].get(), attributesInterface( renderer ) ); + ConstObjectPtr sample = samples[0]; + if( auto capsule = runTimeCast( sample.get() ) ) + { + CapsulePtr capsuleCopy = capsule->copy(); + capsuleCopy->setRenderOptions( renderOptions ); + sample = capsuleCopy; + } + m_objectInterface.assign( + renderer->object( name, sample.get(), attributesInterface( renderer ) ), + ObjectInterfaceHandle::RemovalCallback(), + /* isCapsule = */ runTimeCast( sample.get() ) + ); } else { diff --git a/src/GafferScene/RendererAlgo.cpp b/src/GafferScene/RendererAlgo.cpp index 5f9b7549db4..ade0b58ed06 100644 --- a/src/GafferScene/RendererAlgo.cpp +++ b/src/GafferScene/RendererAlgo.cpp @@ -126,6 +126,13 @@ RenderOptions::RenderOptions( const ScenePlug *scene ) includedPurposes = includedPurposesData ? includedPurposesData : g_defaultIncludedPurposes; } +bool RenderOptions::operator==( const RenderOptions &other ) const +{ + // No need to test other fields because they are all derived directly + // from the globals. + return *globals == *other.globals && shutter == other.shutter; +} + bool RenderOptions::purposeIncluded( const CompoundObject *attributes ) const { const auto purposeData = attributes->member( g_purposeAttributeName ); diff --git a/src/GafferSceneModule/RenderBinding.cpp b/src/GafferSceneModule/RenderBinding.cpp index 52c7eefd13d..c2286b12d4e 100644 --- a/src/GafferSceneModule/RenderBinding.cpp +++ b/src/GafferSceneModule/RenderBinding.cpp @@ -451,6 +451,7 @@ void GafferSceneModule::bindRender() .def_readwrite( "deformationBlur", &GafferScene::Private::RendererAlgo::RenderOptions::deformationBlur ) .def_readwrite( "shutter", &GafferScene::Private::RendererAlgo::RenderOptions::shutter ) .def_readwrite( "includedPurposes", &GafferScene::Private::RendererAlgo::RenderOptions::includedPurposes ) + .def( self == self ) ; def( "objectSamples", &objectSamplesWrapper, ( arg( "objectPlug" ), arg( "sampleTimes" ), arg( "hash" ) = object(), arg( "_copy" ) = true ) ); From 2daface7cd004e510671cce218bac491eea5feeb Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 27 Sep 2023 14:21:29 +0100 Subject: [PATCH 8/8] Capsule, RenderController : Preserve ABI compatibility Although the members in question are private, we still need to preserve `sizeof( Capsule )` and `sizeof( RenderController )` if we are to include the RenderOptions changes in `1.3.x`. We'll revert this commit as soon as it is merged to main, so `1.4` will get a cleaner version of the code. --- include/GafferScene/Capsule.h | 1 - include/GafferScene/RenderController.h | 10 +++++- src/GafferScene/Capsule.cpp | 32 ++++++++++++++----- src/GafferScene/RenderController.cpp | 43 +++++++++++++------------- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/include/GafferScene/Capsule.h b/include/GafferScene/Capsule.h index fefc6913345..4856bfbc4e7 100644 --- a/include/GafferScene/Capsule.h +++ b/include/GafferScene/Capsule.h @@ -112,7 +112,6 @@ class GAFFERSCENE_API Capsule : public IECoreScenePreview::Procedural const ScenePlug *m_scene; ScenePlug::ScenePath m_root; Gaffer::ConstContextPtr m_context; - std::optional m_renderOptions; }; diff --git a/include/GafferScene/RenderController.h b/include/GafferScene/RenderController.h index dc8e1cad1bb..c257cb10088 100644 --- a/include/GafferScene/RenderController.h +++ b/include/GafferScene/RenderController.h @@ -126,6 +126,13 @@ class GAFFERSCENE_API RenderController : public Gaffer::Signals::Trackable AllGlobalComponents = GlobalsGlobalComponent | SetsGlobalComponent | RenderSetsGlobalComponent | CameraOptionsGlobalComponent | TransformBlurGlobalComponent | DeformationBlurGlobalComponent | IncludedPurposesGlobalComponent }; + struct Unused + { + bool unused1; + bool unused2; + Imath::V2f unused3; + }; + void plugDirtied( const Gaffer::Plug *plug ); void contextChanged( const IECore::InternedString &name ); void requestUpdate(); @@ -159,7 +166,8 @@ class GAFFERSCENE_API RenderController : public Gaffer::Signals::Trackable std::vector > m_sceneGraphs; unsigned m_dirtyGlobalComponents; unsigned m_changedGlobalComponents; - Private::RendererAlgo::RenderOptions m_renderOptions; + std::unique_ptr m_renderOptions; + Unused m_unused; Private::RendererAlgo::RenderSets m_renderSets; std::unique_ptr m_lightLinks; IECoreScenePreview::Renderer::ObjectInterfacePtr m_defaultCamera; diff --git a/src/GafferScene/Capsule.cpp b/src/GafferScene/Capsule.cpp index 76764d02791..221dc405fe3 100644 --- a/src/GafferScene/Capsule.cpp +++ b/src/GafferScene/Capsule.cpp @@ -45,6 +45,8 @@ #include "boost/bind/bind.hpp" +#include + using namespace boost::placeholders; using namespace IECore; using namespace IECoreScene; @@ -53,6 +55,7 @@ using namespace GafferScene; namespace { + Gaffer::Context *capsuleContext( const Context &context ) { Gaffer::Context *result = new Gaffer::Context( context ); @@ -62,6 +65,10 @@ namespace result->remove( ScenePlug::scenePathContextName ); return result; } + +std::mutex g_renderOptionsMutex; +std::unordered_map g_renderOptions; + } IE_CORE_DEFINEOBJECTTYPEDESCRIPTION( Capsule ); @@ -84,6 +91,8 @@ Capsule::Capsule( Capsule::~Capsule() { + std::unique_lock renderOptionsLock( g_renderOptionsMutex ); + g_renderOptions.erase( this ); } bool Capsule::isEqualTo( const IECore::Object *other ) const @@ -104,14 +113,14 @@ void Capsule::hash( IECore::MurmurHash &h ) const Procedural::hash( h ); h.append( m_hash ); - if( m_renderOptions ) + if( auto renderOptions = getRenderOptions() ) { // Hash only what affects our rendering, not everything in // `RenderOptions::globals`. - h.append( m_renderOptions->transformBlur ); - h.append( m_renderOptions->deformationBlur ); - h.append( m_renderOptions->shutter ); - m_renderOptions->includedPurposes->hash( h ); + h.append( renderOptions->transformBlur ); + h.append( renderOptions->deformationBlur ); + h.append( renderOptions->shutter ); + renderOptions->includedPurposes->hash( h ); } } @@ -157,7 +166,7 @@ void Capsule::render( IECoreScenePreview::Renderer *renderer ) const { throwIfNoScene(); ScenePlug::GlobalScope scope( m_context.get() ); - std::optional renderOptions = m_renderOptions; + std::optional renderOptions = getRenderOptions(); if( !renderOptions ) { renderOptions = GafferScene::Private::RendererAlgo::RenderOptions( m_scene ); @@ -189,12 +198,19 @@ void Capsule::setRenderOptions( const GafferScene::Private::RendererAlgo::Render // This is not pretty, but it allows the capsule to render with the correct // motion blur and `includedPurposes`, taken from the downstream node being // rendered rather than from the capsule's own globals. - m_renderOptions = renderOptions; + std::unique_lock renderOptionsLock( g_renderOptionsMutex ); + g_renderOptions[this] = renderOptions; } std::optional Capsule::getRenderOptions() const { - return m_renderOptions; + std::unique_lock renderOptionsLock( g_renderOptionsMutex ); + auto it = g_renderOptions.find( this ); + if( it != g_renderOptions.end() ) + { + return it->second; + } + return std::nullopt; } void Capsule::throwIfNoScene() const diff --git a/src/GafferScene/RenderController.cpp b/src/GafferScene/RenderController.cpp index f5a32cba0bb..5150c2c7cbb 100644 --- a/src/GafferScene/RenderController.cpp +++ b/src/GafferScene/RenderController.cpp @@ -380,7 +380,7 @@ class RenderController::SceneGraph // Root - get attributes from globals. if( changedGlobals & GlobalsGlobalComponent ) { - if( updateAttributes( controller->m_renderOptions.globals.get() ) ) + if( updateAttributes( controller->m_renderOptions->globals.get() ) ) { m_changedComponents |= AttributesComponent; } @@ -401,7 +401,7 @@ class RenderController::SceneGraph // If attributes have changed, need to check if this has affected our motion sample times if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & TransformBlurGlobalComponent ) ) { - if( Private::RendererAlgo::transformMotionTimes( controller->m_renderOptions, m_fullAttributes.get(), m_transformTimes ) ) + if( Private::RendererAlgo::transformMotionTimes( *controller->m_renderOptions, m_fullAttributes.get(), m_transformTimes ) ) { m_dirtyComponents |= TransformComponent; } @@ -409,7 +409,7 @@ class RenderController::SceneGraph if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & DeformationBlurGlobalComponent ) ) { - if( Private::RendererAlgo::deformationMotionTimes( controller->m_renderOptions, m_fullAttributes.get(), m_deformationTimes ) ) + if( Private::RendererAlgo::deformationMotionTimes( *controller->m_renderOptions, m_fullAttributes.get(), m_deformationTimes ) ) { m_dirtyComponents |= ObjectComponent; } @@ -442,7 +442,7 @@ class RenderController::SceneGraph if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & IncludedPurposesGlobalComponent ) ) { const bool purposeIncludedPreviously = m_purposeIncluded; - m_purposeIncluded = controller->m_renderOptions.purposeIncluded( m_fullAttributes.get() ); + m_purposeIncluded = controller->m_renderOptions->purposeIncluded( m_fullAttributes.get() ); if( m_purposeIncluded != purposeIncludedPreviously ) { // We'll need to hide or show the object by considering `m_purposeIncluded` in @@ -487,7 +487,7 @@ class RenderController::SceneGraph m_objectHash = MurmurHash(); } - if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), *controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) ) { m_changedComponents |= ObjectComponent; } @@ -511,7 +511,7 @@ class RenderController::SceneGraph { // Failed to apply attributes - must replace entire object. m_objectHash = MurmurHash(); - if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), *controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) ) { m_changedComponents |= ObjectComponent; controller->m_failedAttributeEdits++; @@ -1347,7 +1347,8 @@ RenderController::RenderController( const ConstScenePlugPtr &scene, const Gaffer m_updateRequested( false ), m_failedAttributeEdits( 0 ), m_dirtyGlobalComponents( NoGlobalComponent ), - m_changedGlobalComponents( NoGlobalComponent ) + m_changedGlobalComponents( NoGlobalComponent ), + m_renderOptions( make_unique() ) { for( int i = SceneGraph::FirstType; i <= SceneGraph::LastType; ++i ) { @@ -1627,43 +1628,43 @@ void RenderController::updateInternal( const ProgressCallback &callback, const I if( m_dirtyGlobalComponents & GlobalsGlobalComponent ) { RenderOptions renderOptions( m_scene.get() ); - Private::RendererAlgo::outputOptions( renderOptions.globals.get(), m_renderOptions.globals.get(), m_renderer.get() ); - Private::RendererAlgo::outputOutputs( m_scene.get(), renderOptions.globals.get(), m_renderOptions.globals.get(), m_renderer.get() ); - if( *renderOptions.globals != *m_renderOptions.globals ) + Private::RendererAlgo::outputOptions( renderOptions.globals.get(), m_renderOptions->globals.get(), m_renderer.get() ); + Private::RendererAlgo::outputOutputs( m_scene.get(), renderOptions.globals.get(), m_renderOptions->globals.get(), m_renderer.get() ); + if( *renderOptions.globals != *m_renderOptions->globals ) { m_changedGlobalComponents |= GlobalsGlobalComponent; } - if( cameraGlobalsChanged( renderOptions.globals.get(), m_renderOptions.globals.get(), m_scene.get() ) ) + if( cameraGlobalsChanged( renderOptions.globals.get(), m_renderOptions->globals.get(), m_scene.get() ) ) { m_changedGlobalComponents |= CameraOptionsGlobalComponent; } - if( *renderOptions.includedPurposes != *m_renderOptions.includedPurposes ) + if( *renderOptions.includedPurposes != *m_renderOptions->includedPurposes ) { m_changedGlobalComponents |= IncludedPurposesGlobalComponent; } - if( renderOptions.shutter != m_renderOptions.shutter || renderOptions.transformBlur != m_renderOptions.transformBlur ) + if( renderOptions.shutter != m_renderOptions->shutter || renderOptions.transformBlur != m_renderOptions->transformBlur ) { m_changedGlobalComponents |= TransformBlurGlobalComponent; } - if( renderOptions.shutter != m_renderOptions.shutter || renderOptions.deformationBlur != m_renderOptions.deformationBlur ) + if( renderOptions.shutter != m_renderOptions->shutter || renderOptions.deformationBlur != m_renderOptions->deformationBlur ) { m_changedGlobalComponents |= DeformationBlurGlobalComponent; } - if( renderOptions.shutter != m_renderOptions.shutter ) + if( renderOptions.shutter != m_renderOptions->shutter ) { m_changedGlobalComponents |= CameraOptionsGlobalComponent; } - m_renderOptions = renderOptions; + *m_renderOptions = renderOptions; } if( ( m_dirtyGlobalComponents & CameraShutterGlobalComponent ) && !( m_dirtyGlobalComponents & GlobalsGlobalComponent ) ) { // Shutter override from a camera may have changed, and won't have been covered by // the block above (because the globals weren't dirty). - const V2f shutter = SceneAlgo::shutter( m_renderOptions.globals.get(), m_scene.get() ); - if( shutter != m_renderOptions.shutter ) + const V2f shutter = SceneAlgo::shutter( m_renderOptions->globals.get(), m_scene.get() ); + if( shutter != m_renderOptions->shutter ) { - m_renderOptions.shutter = shutter; + m_renderOptions->shutter = shutter; m_changedGlobalComponents |= ( DeformationBlurGlobalComponent | TransformBlurGlobalComponent ); } } @@ -1774,7 +1775,7 @@ void RenderController::updateDefaultCamera() return; } - const StringData *cameraOption = m_renderOptions.globals->member( g_cameraGlobalName ); + const StringData *cameraOption = m_renderOptions->globals->member( g_cameraGlobalName ); m_defaultCamera = nullptr; if( cameraOption && !cameraOption->readable().empty() ) { @@ -1782,7 +1783,7 @@ void RenderController::updateDefaultCamera() } CameraPtr defaultCamera = new IECoreScene::Camera; - SceneAlgo::applyCameraGlobals( defaultCamera.get(), m_renderOptions.globals.get(), m_scene.get() ); + SceneAlgo::applyCameraGlobals( defaultCamera.get(), m_renderOptions->globals.get(), m_scene.get() ); IECoreScenePreview::Renderer::AttributesInterfacePtr defaultAttributes = m_renderer->attributes( m_scene->attributesPlug()->defaultValue() ); ConstStringDataPtr name = new StringData( "gaffer:defaultCamera" ); m_defaultCamera = m_renderer->camera( name->readable(), defaultCamera.get(), defaultAttributes.get() );