Skip to content

Commit

Permalink
Merge pull request #4029 from danieldresser-ie/instancerEncapNoCon
Browse files Browse the repository at this point in the history
Encapsulation in Instancer
  • Loading branch information
johnhaddon authored Dec 15, 2020
2 parents 1c4654e + 72a8cac commit e95d3c1
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 29 deletions.
8 changes: 8 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
0.59.x.x (relative to 0.59.0.0)
========

Features
--------

- Instancer : Added `encapsulateInstanceGroups` plug, which outputs the instances within capsules. This has the same performance benefits as using an Encapsulate node downstream, with the following additional benefits :
- Significantly improved performance when the prototypes define sets. A benchmarch with 1 million instances saw set generation time go from 10s using a downstream Encapsulate node to 0.002s using `encapsulateInstanceGroups`.
- Fewer unnecessary capsule invalidations, resulting in fewer interactive rendering updates.
- Convenience.

Fixes
-----

Expand Down
50 changes: 25 additions & 25 deletions include/GafferScene/BranchCreator.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,31 @@ class GAFFERSCENE_API BranchCreator : public FilteredSceneProcessor
virtual IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const;
//@}

// Computes the relevant parent and branch paths for computing the result
// at the specified path. Returns a PathMatcher::Result to describe where path is
// relative to the parent, as follows :
//
// AncestorMatch
//
// The path is on a branch below the parent, parentPath and branchPath
// are filled in appropriately, and branchPath will not be empty.
//
// ExactMatch
//
// The path is at the parent exactly, parentPath will be filled
// in appropriately and branchPath will be empty.
//
// DescendantMatch
//
// The path is above one or more parents. Neither parentPath nor branchPath
// will be filled in.
//
// NoMatch
//
// The path is a direct pass through from the input - neither
// parentPath nor branchPath will be filled in.
IECore::PathMatcher::Result parentAndBranchPaths( const ScenePath &path, ScenePath &parentPath, ScenePath &branchPath ) const;

private :

/// Returns the path specified by `parentPlug()`, only if it is non-empty
Expand Down Expand Up @@ -179,31 +204,6 @@ class GAFFERSCENE_API BranchCreator : public FilteredSceneProcessor
IECore::PathMatcher parentPathsForSet( const IECore::InternedString &setName, const Gaffer::Context *context ) const;
bool affectsParentPathsForSet( const Gaffer::Plug *input ) const;

// Computes the relevant parent and branch paths for computing the result
// at the specified path. Returns a PathMatcher::Result to describe where path is
// relative to the parent, as follows :
//
// AncestorMatch
//
// The path is on a branch below the parent, parentPath and branchPath
// are filled in appropriately, and branchPath will not be empty.
//
// ExactMatch
//
// The path is at the parent exactly, parentPath will be filled
// in appropriately and branchPath will be empty.
//
// DescendantMatch
//
// The path is above one or more parents. Neither parentPath nor branchPath
// will be filled in.
//
// NoMatch
//
// The path is a direct pass through from the input - neither
// parentPath nor branchPath will be filled in.
IECore::PathMatcher::Result parentAndBranchPaths( const ScenePath &path, ScenePath &parentPath, ScenePath &branchPath ) const;

static size_t g_firstPlugIndex;

};
Expand Down
15 changes: 15 additions & 0 deletions include/GafferScene/Instancer.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ 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 :
Expand All @@ -121,10 +124,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;
Expand All @@ -133,6 +142,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 );
Expand All @@ -143,6 +155,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;

Expand Down
55 changes: 55 additions & 0 deletions python/GafferSceneTest/InstancerTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,40 @@ 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 returning to a previously cached value after
# changing the seeds doesn't pull an expired Capsule out of the cache )
freezeTransform["enabled"].setValue( False )
self.assertScenesEqual( unencap["out"], instancer["out"] )

def testThreading( self ) :

sphere = IECoreScene.SpherePrimitive()
Expand Down Expand Up @@ -1122,6 +1156,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()
Expand Down
21 changes: 21 additions & 0 deletions python/GafferSceneUI/InstancerUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,27 @@

],

"encapsulateInstanceGroups" : [

"description",
"""
Converts 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
instance locations, which prevents editing but improves
performance. This option should be preferred to a downstream
Encapsulate node because it has the following benefits :
- Substantially improved performance when the prototypes
define sets.
- Fewer unnecessary updates during interactive rendering.
""",
"label", "Instance Groups",

"layout:section", "Settings.Encapsulation",

],

}

)
Loading

0 comments on commit e95d3c1

Please sign in to comment.