diff --git a/Changes.md b/Changes.md index 253ddbd3946..17e6783b72d 100644 --- a/Changes.md +++ b/Changes.md @@ -7,6 +7,7 @@ Features -------- - Unencapsulate : Added new node to expand capsules created by Encapsulate back into regular scene hierarchy. This discards the performance advantages of working with capsules, but is useful for debugging, or when it is necessary to alter a capsule's contents downstream. +- Instancer : Added "encapsulateInstanceGroups" option, which outputs the instances within capsules. This improves performance in some cases. Improvements ------------ diff --git a/include/GafferScene/Instancer.h b/include/GafferScene/Instancer.h index 887a46637bb..5ea931d5d35 100644 --- a/include/GafferScene/Instancer.h +++ b/include/GafferScene/Instancer.h @@ -96,8 +96,12 @@ class GAFFERSCENE_API Instancer : public BranchCreator Gaffer::StringPlug *attributePrefixPlug(); const Gaffer::StringPlug *attributePrefixPlug() const; + Gaffer::BoolPlug *encapsulateInstanceGroupsPlug(); + const Gaffer::BoolPlug *encapsulateInstanceGroupsPlug() const; + void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; + protected : void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; @@ -121,10 +125,16 @@ class GAFFERSCENE_API Instancer : public BranchCreator void hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; IECore::ConstObjectPtr computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const override; +IECore::ConstObjectPtr computeObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const override; + bool affectsBranchChildNames( const Gaffer::Plug *input ) const override; void hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const override; + bool affectsBranchSetNames( const Gaffer::Plug *input ) const override; void hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const override; @@ -133,6 +143,9 @@ class GAFFERSCENE_API Instancer : public BranchCreator void hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; + void hashSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const override; + IECore::ConstPathMatcherDataPtr computeSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent ) const override; + private : IE_CORE_FORWARDDECLARE( EngineData ); @@ -143,6 +156,9 @@ class GAFFERSCENE_API Instancer : public BranchCreator Gaffer::AtomicCompoundDataPlug *prototypeChildNamesPlug(); const Gaffer::AtomicCompoundDataPlug *prototypeChildNamesPlug() const; + GafferScene::ScenePlug *capsuleScenePlug(); + const GafferScene::ScenePlug *capsuleScenePlug() const; + ConstEngineDataPtr engine( const ScenePath &parentPath, const Gaffer::Context *context ) const; void engineHash( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const; diff --git a/python/GafferSceneTest/InstancerTest.py b/python/GafferSceneTest/InstancerTest.py index af65165de76..05008be2e39 100644 --- a/python/GafferSceneTest/InstancerTest.py +++ b/python/GafferSceneTest/InstancerTest.py @@ -125,6 +125,39 @@ def test( self ) : self.assertEqual( instancer["out"].bound( instancePath ), sphere.bound() ) self.assertEqual( instancer["out"].childNames( instancePath ), IECore.InternedStringVectorData() ) + # Test encapsulation options + encapInstancer = GafferScene.Instancer() + encapInstancer["in"].setInput( seedsInput["out"] ) + encapInstancer["prototypes"].setInput( instanceInput["out"] ) + encapInstancer["parent"].setValue( "/seeds" ) + encapInstancer["name"].setValue( "instances" ) + encapInstancer["encapsulateInstanceGroups"].setValue( True ) + + unencapFilter = GafferScene.PathFilter() + unencapFilter["paths"].setValue( IECore.StringVectorData( [ "/..." ] ) ) + + unencap = GafferScene.Unencapsulate() + unencap["in"].setInput( encapInstancer["out"] ) + unencap["filter"].setInput( unencapFilter["out"] ) + + self.assertTrue( isinstance( encapInstancer["out"].object( "/seeds/instances/sphere/" ), GafferScene.Capsule ) ) + self.assertEqual( encapInstancer["out"].childNames( "/seeds/instances/sphere/" ), IECore.InternedStringVectorData() ) + self.assertScenesEqual( unencap["out"], instancer["out"] ) + + # Edit seeds object + freezeTransform = GafferScene.FreezeTransform() + freezeTransform["in"].setInput( seedsInput["out"] ) + freezeTransform["filter"].setInput( unencapFilter["out"] ) + + instancer["in"].setInput( freezeTransform["out"] ) + encapInstancer["in"].setInput( freezeTransform["out"] ) + + self.assertScenesEqual( unencap["out"], instancer["out"] ) + + # Then set it back ( to make sure that we don't pull expired Capsules out of the cache ) + freezeTransform["enabled"].setValue( False ) + self.assertScenesEqual( unencap["out"], instancer["out"] ) + def testThreading( self ) : sphere = IECoreScene.SpherePrimitive() @@ -1122,6 +1155,27 @@ def testSets( self ) : } ) + # Test encapsulation options + encapInstancer = GafferScene.Instancer() + encapInstancer["in"].setInput( objectToScene["out"] ) + encapInstancer["prototypes"].setInput( instances["out"] ) + encapInstancer["parent"].setValue( "/object" ) + encapInstancer["prototypeIndex"].setValue( "index" ) + encapInstancer["encapsulateInstanceGroups"].setValue( True ) + + unencapFilter = GafferScene.PathFilter() + unencapFilter["paths"].setValue( IECore.StringVectorData( [ "/..." ] ) ) + + unencap = GafferScene.Unencapsulate() + unencap["in"].setInput( encapInstancer["out"] ) + unencap["filter"].setInput( unencapFilter["out"] ) + + # Sets should be empty while encapsulated + self.assertEqual( encapInstancer["out"].set( "sphereSet" ).value.paths(), [] ) + self.assertEqual( encapInstancer["out"].set( "cubeSet" ).value.paths(), [] ) + # But should match after unencapsulating + self.assertScenesEqual( unencap["out"], instancer["out"] ) + def testSetsWithDeepPrototypeRoots( self ) : script = self.buildPrototypeRootsScript() diff --git a/python/GafferSceneUI/InstancerUI.py b/python/GafferSceneUI/InstancerUI.py index c7457673d6e..cb79f43611b 100644 --- a/python/GafferSceneUI/InstancerUI.py +++ b/python/GafferSceneUI/InstancerUI.py @@ -285,6 +285,24 @@ ], + "encapsulateInstanceGroups" : [ + + "description", + """ + Convert each group of instances into a capsule, which won't + be expanded until you Unencapsulate or render. When keeping + these locations encapsulated, downstream nodes can't see the + list of instances, which prevents editing them, but saves + performance. In particular, using the encapsulation built + into the instancer saves expanding the sets from the prototypes + prematurely, which can be quite expensive. + """, + "label", "Instance Groups", + + "layout:section", "Settings.Encapsulate", + + ], + } ) diff --git a/src/GafferScene/Instancer.cpp b/src/GafferScene/Instancer.cpp index 3d75f86384a..41daccd99ca 100644 --- a/src/GafferScene/Instancer.cpp +++ b/src/GafferScene/Instancer.cpp @@ -37,6 +37,7 @@ #include "GafferScene/Instancer.h" +#include "GafferScene/Capsule.h" #include "GafferScene/SceneAlgo.h" #include "GafferScene/Private/ChildNamesMap.h" @@ -513,8 +514,16 @@ Instancer::Instancer( const std::string &name ) addChild( new StringPlug( "scale", Plug::In ) ); addChild( new StringPlug( "attributes", Plug::In ) ); addChild( new StringPlug( "attributePrefix", Plug::In ) ); + addChild( new BoolPlug( "encapsulateInstanceGroups", Plug::In ) ); addChild( new ObjectPlug( "__engine", Plug::Out, NullObject::defaultNullObject() ) ); addChild( new AtomicCompoundDataPlug( "__prototypeChildNames", Plug::Out, new CompoundData ) ); + addChild( new ScenePlug( "__capsuleScene", Plug::Out ) ); + + capsuleScenePlug()->boundPlug()->setInput( outPlug()->boundPlug() ); + capsuleScenePlug()->transformPlug()->setInput( outPlug()->transformPlug() ); + capsuleScenePlug()->attributesPlug()->setInput( outPlug()->attributesPlug() ); + capsuleScenePlug()->setNamesPlug()->setInput( outPlug()->setNamesPlug() ); + capsuleScenePlug()->globalsPlug()->setInput( outPlug()->globalsPlug() ); } Instancer::~Instancer() @@ -641,24 +650,44 @@ const Gaffer::StringPlug *Instancer::attributePrefixPlug() const return getChild( g_firstPlugIndex + 11 ); } +Gaffer::BoolPlug *Instancer::encapsulateInstanceGroupsPlug() +{ + return getChild( g_firstPlugIndex + 12 ); +} + +const Gaffer::BoolPlug *Instancer::encapsulateInstanceGroupsPlug() const +{ + return getChild( g_firstPlugIndex + 12 ); +} + Gaffer::ObjectPlug *Instancer::enginePlug() { - return getChild( g_firstPlugIndex + 12 ); + return getChild( g_firstPlugIndex + 13 ); } const Gaffer::ObjectPlug *Instancer::enginePlug() const { - return getChild( g_firstPlugIndex + 12 ); + return getChild( g_firstPlugIndex + 13 ); } Gaffer::AtomicCompoundDataPlug *Instancer::prototypeChildNamesPlug() { - return getChild( g_firstPlugIndex + 13 ); + return getChild( g_firstPlugIndex + 14 ); } const Gaffer::AtomicCompoundDataPlug *Instancer::prototypeChildNamesPlug() const { - return getChild( g_firstPlugIndex + 13 ); + return getChild( g_firstPlugIndex + 14 ); +} + +GafferScene::ScenePlug *Instancer::capsuleScenePlug() +{ + return getChild( g_firstPlugIndex + 15 ); +} + +const GafferScene::ScenePlug *Instancer::capsuleScenePlug() const +{ + return getChild( g_firstPlugIndex + 15 ); } void Instancer::affects( const Plug *input, AffectedPlugsContainer &outputs ) const @@ -688,6 +717,33 @@ void Instancer::affects( const Plug *input, AffectedPlugsContainer &outputs ) co { outputs.push_back( prototypeChildNamesPlug() ); } + + // For the affects of our output plug, we can mostly rely on BranchCreator's mechanism driven + // by affectsBranchObject etc., but for these 3 plugs, we have an overridden hash/compute + // which in addition to everything that BranchCreator handles, are also affected by + // encapsulateInstanceGroupsPlug() + if( input == encapsulateInstanceGroupsPlug() ) + { + outputs.push_back( outPlug()->objectPlug() ); + outputs.push_back( outPlug()->childNamesPlug() ); + outputs.push_back( outPlug()->setPlug() ); + } + + // The capsule scene depends on all the same things as the regular output scene ( aside from not + // being affected by the encapsulate plug, which always must be true when it's evaluated anyway ), + // so we can leverage the logic in BranchCreator to drive it + if( input == outPlug()->objectPlug() ) + { + outputs.push_back( capsuleScenePlug()->objectPlug() ); + } + if( input == outPlug()->childNamesPlug() ) + { + outputs.push_back( capsuleScenePlug()->childNamesPlug() ); + } + if( input == outPlug()->setPlug() ) + { + outputs.push_back( capsuleScenePlug()->setPlug() ); + } } void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const @@ -1102,6 +1158,62 @@ IECore::ConstObjectPtr Instancer::computeBranchObject( const ScenePath &parentPa } } +void Instancer::hashObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const +{ + if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) + { + // Handling this special case here means an extra call to parentAndBranchPaths + // when we're encapsulating and we're not inside a branch - this is a small + // unnecessary cost, but by falling back to just using BranchCreator hashObject + // when branchPath.size() != 2, we are able to just use all the logic from + // BranchCreator, without exposing any new API surface + ScenePath parentPath, branchPath; + parentAndBranchPaths( path, parentPath, branchPath ); + if( branchPath.size() == 2 ) + { + BranchCreator::hashBranchObject( parentPath, branchPath, context, h ); + h.append( reinterpret_cast( this ) ); + /// We need to include anything that will affect how the capsule will expand + /// \todo : This should use `h.append( Capsule::capsuleDirtyCount( prototypesPlug() ) );` + /// but we can't use that currently because of the undesired dependency of the + /// capsule on the global shutter of the source scene + h.append( prototypesPlug()->dirtyCount() ); + engineHash( parentPath, context, h ); + h.append( context->hash() ); + outPlug()->boundPlug()->hash( h ); + return; + } + } + + BranchCreator::hashObject( path, context, parent, h ); +} + +IECore::ConstObjectPtr Instancer::computeObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const +{ + if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) + { + ScenePath parentPath, branchPath; + parentAndBranchPaths( path, parentPath, branchPath ); + if( branchPath.size() == 2 ) + { + Gaffer::ContextPtr capsuleContext = new Context( *context ); + capsuleContext->remove( ScenePlug::scenePathContextName ); + + return new Capsule( + capsuleScenePlug(), + context->get( ScenePlug::scenePathContextName ) , + std::move( capsuleContext ), + outPlug()->objectPlug()->hash(), + outPlug()->boundPlug()->getValue() + ); + + } + } + + return BranchCreator::computeObject( path, context, parent ); +} + + bool Instancer::affectsBranchChildNames( const Gaffer::Plug *input ) const { return @@ -1173,6 +1285,37 @@ IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchChildNames( con } } +void Instancer::hashChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const +{ + if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) + { + ScenePath parentPath, branchPath; + parentAndBranchPaths( path, parentPath, branchPath ); + if( branchPath.size() == 2 ) + { + h = outPlug()->childNamesPlug()->defaultValue()->Object::hash(); + return; + } + } + + BranchCreator::hashChildNames( path, context, parent, h ); +} + +IECore::ConstInternedStringVectorDataPtr Instancer::computeChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const +{ + if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) + { + ScenePath parentPath, branchPath; + parentAndBranchPaths( path, parentPath, branchPath ); + if( branchPath.size() == 2 ) + { + return outPlug()->childNamesPlug()->defaultValue(); + } + } + + return BranchCreator::computeChildNames( path, context, parent ); +} + bool Instancer::affectsBranchSetNames( const Gaffer::Plug *input ) const { return input == prototypesPlug()->setNamesPlug(); @@ -1241,6 +1384,27 @@ IECore::ConstPathMatcherDataPtr Instancer::computeBranchSet( const ScenePath &pa return outputSetData; } +void Instancer::hashSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const +{ + if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) + { + h = inPlug()->setPlug()->hash(); + return; + } + + BranchCreator::hashSet( setName, context, parent, h ); +} + +IECore::ConstPathMatcherDataPtr Instancer::computeSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent ) const +{ + if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) + { + return inPlug()->setPlug()->getValue(); + } + + return BranchCreator::computeSet( setName, context, parent ); +} + Instancer::ConstEngineDataPtr Instancer::engine( const ScenePath &parentPath, const Gaffer::Context *context ) const { ScenePlug::PathScope scope( context, parentPath );