Skip to content

Commit

Permalink
Instancer : Added encapsulateInstanceGroups
Browse files Browse the repository at this point in the history
  • Loading branch information
danieldresser committed Dec 2, 2020
1 parent ff2a32c commit de5ad08
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 4 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------
Expand Down
16 changes: 16 additions & 0 deletions include/GafferScene/Instancer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 );
Expand All @@ -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;

Expand Down
54 changes: 54 additions & 0 deletions python/GafferSceneTest/InstancerTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
18 changes: 18 additions & 0 deletions python/GafferSceneUI/InstancerUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",

],

}

)
172 changes: 168 additions & 4 deletions src/GafferScene/Instancer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

#include "GafferScene/Instancer.h"

#include "GafferScene/Capsule.h"
#include "GafferScene/SceneAlgo.h"

#include "GafferScene/Private/ChildNamesMap.h"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -641,24 +650,44 @@ const Gaffer::StringPlug *Instancer::attributePrefixPlug() const
return getChild<StringPlug>( g_firstPlugIndex + 11 );
}

Gaffer::BoolPlug *Instancer::encapsulateInstanceGroupsPlug()
{
return getChild<BoolPlug>( g_firstPlugIndex + 12 );
}

const Gaffer::BoolPlug *Instancer::encapsulateInstanceGroupsPlug() const
{
return getChild<BoolPlug>( g_firstPlugIndex + 12 );
}

Gaffer::ObjectPlug *Instancer::enginePlug()
{
return getChild<ObjectPlug>( g_firstPlugIndex + 12 );
return getChild<ObjectPlug>( g_firstPlugIndex + 13 );
}

const Gaffer::ObjectPlug *Instancer::enginePlug() const
{
return getChild<ObjectPlug>( g_firstPlugIndex + 12 );
return getChild<ObjectPlug>( g_firstPlugIndex + 13 );
}

Gaffer::AtomicCompoundDataPlug *Instancer::prototypeChildNamesPlug()
{
return getChild<AtomicCompoundDataPlug>( g_firstPlugIndex + 13 );
return getChild<AtomicCompoundDataPlug>( g_firstPlugIndex + 14 );
}

const Gaffer::AtomicCompoundDataPlug *Instancer::prototypeChildNamesPlug() const
{
return getChild<AtomicCompoundDataPlug>( g_firstPlugIndex + 13 );
return getChild<AtomicCompoundDataPlug>( g_firstPlugIndex + 14 );
}

GafferScene::ScenePlug *Instancer::capsuleScenePlug()
{
return getChild<ScenePlug>( g_firstPlugIndex + 15 );
}

const GafferScene::ScenePlug *Instancer::capsuleScenePlug() const
{
return getChild<ScenePlug>( g_firstPlugIndex + 15 );
}

void Instancer::affects( const Plug *input, AffectedPlugsContainer &outputs ) const
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<uint64_t>( 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::ScenePath>( 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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit de5ad08

Please sign in to comment.