Skip to content

Commit

Permalink
Merge pull request #5453 from danieldresser-ie/instancerCapsule
Browse files Browse the repository at this point in the history
Instancer Capsule
  • Loading branch information
johnhaddon authored Nov 10, 2023
2 parents f877952 + 3adc801 commit 3db4256
Show file tree
Hide file tree
Showing 15 changed files with 1,679 additions and 248 deletions.
10 changes: 10 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
1.3.x.x (relative to 1.3.6.1)
=======

Improvements
------------

- Instancer :
- Improved scene generation for encapsulated instancers significantly, with some production scenes now generating 5-7x faster.
- Added `omitDuplicateIds` plug, to determine whether points with duplicate IDs are ignored or should trigger an error.

API
---

- Capsule : Added protected `renderOptions()` and `throwIfNoScene()` methods.

1.3.6.1 (relative to 1.3.6.0)
=======
Expand Down
8 changes: 7 additions & 1 deletion include/GafferScene/Capsule.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,16 @@ class GAFFERSCENE_API Capsule : public IECoreScenePreview::Procedural
void setRenderOptions( const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions );
std::optional<GafferScene::Private::RendererAlgo::RenderOptions> getRenderOptions() const;

private :
protected :

// Returns the current render options - this will be the override if setRenderOptions has been called,
// otherwise it will construct render options based on the `scene()`.
GafferScene::Private::RendererAlgo::RenderOptions renderOptions() const;

void throwIfNoScene() const;

private :

IECore::MurmurHash m_hash;
Imath::Box3f m_bound;
// We don't own a reference to `m_scene` because it could cause its deletion
Expand Down
23 changes: 17 additions & 6 deletions include/GafferScene/Instancer.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@

#include "GafferScene/Export.h"
#include "GafferScene/BranchCreator.h"
#include "GafferScene/Capsule.h"

namespace GafferSceneModule
{

// Forward declaration to enable friend declaration.
void bindHierarchy();

} // namespace GafferSceneModule

namespace GafferScene
{
Expand Down Expand Up @@ -117,6 +126,9 @@ class GAFFERSCENE_API Instancer : public BranchCreator
Gaffer::StringPlug *idPlug();
const Gaffer::StringPlug *idPlug() const;

Gaffer::BoolPlug *omitDuplicateIdsPlug();
const Gaffer::BoolPlug *omitDuplicateIdsPlug() const;

Gaffer::StringPlug *positionPlug();
const Gaffer::StringPlug *positionPlug() const;

Expand Down Expand Up @@ -211,13 +223,11 @@ class GAFFERSCENE_API Instancer : public BranchCreator
private :

IE_CORE_FORWARDDECLARE( EngineData );
IE_CORE_FORWARDDECLARE( InstancerCapsule );

Gaffer::ObjectPlug *enginePlug();
const Gaffer::ObjectPlug *enginePlug() const;

Gaffer::AtomicCompoundDataPlug *prototypeChildNamesPlug();
const Gaffer::AtomicCompoundDataPlug *prototypeChildNamesPlug() const;

GafferScene::ScenePlug *capsuleScenePlug();
const GafferScene::ScenePlug *capsuleScenePlug() const;

Expand All @@ -230,9 +240,6 @@ class GAFFERSCENE_API Instancer : public BranchCreator
ConstEngineDataPtr engine( const ScenePath &sourcePath, const Gaffer::Context *context ) const;
void engineHash( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const;

IECore::ConstCompoundDataPtr prototypeChildNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const;
void prototypeChildNamesHash( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const;

struct PrototypeScope : public Gaffer::Context::EditableScope
{
PrototypeScope( const Gaffer::ObjectPlug *enginePlug, const Gaffer::Context *context, const ScenePath *parentPath, const ScenePath *branchPath );
Expand All @@ -247,6 +254,10 @@ class GAFFERSCENE_API Instancer : public BranchCreator

static size_t g_firstPlugIndex;

// For bindings
friend void GafferSceneModule::bindHierarchy();
static const std::type_info &instancerCapsuleTypeInfo();

};

IE_CORE_DECLAREPTR( Instancer )
Expand Down
1 change: 1 addition & 0 deletions include/GafferScene/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ enum TypeId
FramingConstraintTypeId = 110633,
MeshNormalsTypeId = 110634,
ImageScatterTypeId = 110635,
InstancerCapsuleTypeId = 110636,

PreviewPlaceholderTypeId = 110647,
PreviewGeometryTypeId = 110648,
Expand Down
268 changes: 267 additions & 1 deletion python/GafferArnoldTest/ArnoldRenderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,7 +1256,7 @@ def __color4fAtUV( self, image, uv ) :
def __arrayToSet( self, a ) :

result = set()
for i in range( 0, arnold.AiArrayGetNumElements( a.contents ) ) :
for i in range( 0, arnold.AiArrayGetNumElements( a.contents ) ) :
if arnold.AiArrayGetType( a.contents ) == arnold.AI_TYPE_STRING :
result.add( arnold.AiArrayGetStr( a, i ) )
else :
Expand Down Expand Up @@ -1478,5 +1478,271 @@ def testCoordinateSystem( self ) :
# node to have been created for ours.
self.assertIsNone( arnold.AiNodeLookUpByName( universe, "/coordinateSystem" ) )

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 1 )
def testInstancerPerf( self ) :

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["sphere"] = GafferScene.Sphere()

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["plane"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphere"]["out"] )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 5 )
def testInstancerEncapsulatePerf( self ) :

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["sphere"] = GafferScene.Sphere()

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["plane"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphere"]["out"] )

s["instancer"]["encapsulateInstanceGroups"].setValue( True )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 1 )
def testInstancerManyPrototypesPerf( self ) :
# Having a context variable set without anything in the prototype being affected by that
# context variable is mostly just going to add stress to the hash cache. This test exists
# mostly for comparison with the encapsulated case below.

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["sphere"] = GafferScene.Sphere()

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["plane"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphere"]["out"] )

s["instancer"]["contextVariables"].addChild( GafferScene.Instancer.ContextVariablePlug( "context" ) )
s["instancer"]["contextVariables"][0]["name"].setValue( "P" )
s["instancer"]["contextVariables"][0]["quantize"].setValue( 0 )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 1 )
def testInstancerManyPrototypesEncapsulatePerf( self ) :
# Having a context variable set ( even without anything in the prototype reading it ), will force
# the encapsulate code path to allocate a bunch of separate prototypes, even if they all end up the same.

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["sphere"] = GafferScene.Sphere()

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["plane"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphere"]["out"] )

s["instancer"]["contextVariables"].addChild( GafferScene.Instancer.ContextVariablePlug( "context" ) )
s["instancer"]["contextVariables"][0]["name"].setValue( "P" )
s["instancer"]["contextVariables"][0]["quantize"].setValue( 0 )

s["instancer"]["encapsulateInstanceGroups"].setValue( True )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 1 )
def testInstancerFewPrototypesPerf( self ) :

# A slightly weird test, but it tests one extreme: there is a context variable, but quantize is
# set so high that all the contexts end up the same, and only one prototype is needed.
# This case is particularly bad for the unencapsulated code path, but quite good for the
# encapsulated path.

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["sphere"] = GafferScene.Sphere()

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["plane"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphere"]["out"] )

s["instancer"]["contextVariables"].addChild( GafferScene.Instancer.ContextVariablePlug( "context" ) )
s["instancer"]["contextVariables"][0]["name"].setValue( "P" )
s["instancer"]["contextVariables"][0]["quantize"].setValue( 100000 )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 1 )
def testInstancerFewPrototypesEncapsulatePerf( self ) :

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["sphere"] = GafferScene.Sphere()

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["plane"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphere"]["out"] )

s["instancer"]["contextVariables"].addChild( GafferScene.Instancer.ContextVariablePlug( "context" ) )
s["instancer"]["contextVariables"][0]["name"].setValue( "P" )
s["instancer"]["contextVariables"][0]["quantize"].setValue( 1000000 )

s["instancer"]["encapsulateInstanceGroups"].setValue( True )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 1 )
def testInstancerWithAttributesPerf( self ) :

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["shuffle"] = GafferScene.ShufflePrimitiveVariables()
s["shuffle"]["in"].setInput( s["plane"]["out"] )
s["shuffle"]["filter"].setInput( s["pathFilter"]["out"] )
for v in [ "A", "B", "C", "D", "E", "F", "G", "H" ]:
s["shuffle"]["shuffles"].addChild( Gaffer.ShufflePlug( "P", v ) )

s["sphere"] = GafferScene.Sphere()

s["sphereAttrs"] = GafferScene.CustomAttributes()
s["sphereAttrs"]["in"].setInput( s["sphere"]["out"] )
for v in [ "I", "J", "K", "L", "M", "N", "O", "P" ]:
s["sphereAttrs"]["attributes"].addChild( Gaffer.NameValuePlug( v, Gaffer.IntPlug( "value", defaultValue = 7 ) ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["shuffle"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphereAttrs"]["out"] )
s["instancer"]["attributes"].setValue( "P N uv A B C D E F G H" )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

@GafferTest.TestRunner.PerformanceTestMethod( repeat = 5 )
def testInstancerWithAttributesEncapsulatePerf( self ) :

s = Gaffer.ScriptNode()

s["plane"] = GafferScene.Plane()
s["plane"]["divisions"].setValue( imath.V2i( 500 ) )

s["pathFilter"] = GafferScene.PathFilter()
s["pathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/plane' ] ) )

s["shuffle"] = GafferScene.ShufflePrimitiveVariables()
s["shuffle"]["in"].setInput( s["plane"]["out"] )
s["shuffle"]["filter"].setInput( s["pathFilter"]["out"] )
for v in [ "A", "B", "C", "D", "E", "F", "G", "H" ]:
s["shuffle"]["shuffles"].addChild( Gaffer.ShufflePlug( "P", v ) )

s["sphere"] = GafferScene.Sphere()

s["sphereAttrs"] = GafferScene.CustomAttributes()
s["sphereAttrs"]["in"].setInput( s["sphere"]["out"] )
for v in [ "I", "J", "K", "L", "M", "N", "O", "P" ]:
s["sphereAttrs"]["attributes"].addChild( Gaffer.NameValuePlug( v, Gaffer.IntPlug( "value", defaultValue = 7 ) ) )

s["instancer"] = GafferScene.Instancer()
s["instancer"]["in"].setInput( s["shuffle"]["out"] )
s["instancer"]["filter"].setInput( s["pathFilter"]["out"] )
s["instancer"]["prototypes"].setInput( s["sphereAttrs"]["out"] )
s["instancer"]["attributes"].setValue( "P N uv A B C D E F G H" )

s["instancer"]["encapsulateInstanceGroups"].setValue( True )

s["render"] = GafferArnold.ArnoldRender()
s["render"]["in"].setInput( s["instancer"]["out"] )

with Gaffer.Context() as c :
c["scene:render:sceneTranslationOnly"] = IECore.BoolData( True )
with GafferTest.TestRunner.PerformanceScope() :
s["render"]["task"].execute()

if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 3db4256

Please sign in to comment.