Skip to content

Commit

Permalink
RenderController : Pass RenderOptions changes to Capsules
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhaddon committed Sep 25, 2023
1 parent 090b4da commit fad41ba
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 7 deletions.
4 changes: 3 additions & 1 deletion Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Fixes
-----

- GraphEditor : Removed dynamic raster-space sizing of focus icon, as it caused excessive overlap with other nodes at certain zoom levels and on certain high resolution displays (#5435).
- 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
---
Expand Down
1 change: 1 addition & 0 deletions include/GafferScene/Private/RendererAlgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions include/GafferScene/RenderController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
99 changes: 99 additions & 0 deletions python/GafferSceneTest/RenderControllerTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
39 changes: 33 additions & 6 deletions src/GafferScene/RenderController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include "GafferScene/RenderController.h"

#include "GafferScene/Capsule.h"
#include "GafferScene/Private/IECoreScenePreview/Placeholder.h"
#include "GafferScene/SceneAlgo.h"

Expand Down Expand Up @@ -173,6 +174,7 @@ struct ObjectInterfaceHandle : public boost::noncopyable
using RemovalCallback = std::function<void ()>;

ObjectInterfaceHandle()
: m_isCapsule( false )
{
}

Expand All @@ -189,14 +191,15 @@ 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 )
{
m_removalCallback();
}
m_objectInterface = p;
m_removalCallback = removalCallback;
m_isCapsule = isCapsule;
}

IECoreScenePreview::Renderer::ObjectInterface *operator->() const
Expand All @@ -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;

};

Expand Down Expand Up @@ -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;
}
Expand All @@ -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++;
Expand Down Expand Up @@ -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<bool>( m_objectInterface );
if( type == NoType || m_drawMode != VisibleSet::Visibility::Visible || !m_purposeIncluded )
Expand Down Expand Up @@ -905,7 +921,7 @@ class RenderController::SceneGraph
if( auto cameraSample = runTimeCast<const Camera>( 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 );
}
}
Expand Down Expand Up @@ -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<const Capsule>( 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<const Capsule>( sample.get() )
);
}
else
{
Expand Down
7 changes: 7 additions & 0 deletions src/GafferScene/RendererAlgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<StringData>( g_purposeAttributeName );
Expand Down
1 change: 1 addition & 0 deletions src/GafferSceneModule/RenderBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) );
Expand Down

0 comments on commit fad41ba

Please sign in to comment.