Skip to content

Commit

Permalink
USDLayerWriter : Refactor using parallelReduceLocations
Browse files Browse the repository at this point in the history
  • Loading branch information
danieldresser-ie authored and johnhaddon committed Feb 11, 2025
1 parent 6482982 commit 4008b88
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 109 deletions.
30 changes: 30 additions & 0 deletions python/GafferUSDTest/USDLayerWriterTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,5 +418,35 @@ def testDispatch( self ) :

self.assertTrue( ( self.temporaryDirectory() / "test.usda" ).is_file() )

def testIdenticalNamesInDifferentGroups( self ) :

# /plane <- modified in layer
# /group
# /plane <- identical

plane = GafferScene.Plane()

group = GafferScene.Group()
group["in"][0].setInput( plane["out"] )

parent = GafferScene.Parent()
parent["in"].setInput( plane["out"] )
parent["children"][0].setInput( group["out"] )
parent["parent"].setValue( "/" )

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

primitiveVariables = GafferScene.PrimitiveVariables()
primitiveVariables["in"].setInput( parent["out"] )
primitiveVariables["filter"].setInput( pathFilter["out"] )
primitiveVariables["primitiveVariables"].addChild( Gaffer.NameValuePlug( "a", 10 ) )

layerFileName, compositionFileName = self.__writeLayerAndComposition( parent["out"], primitiveVariables["out"] )

reader = GafferScene.SceneReader()
reader["fileName"].setValue( compositionFileName )
self.assertScenesEqual( primitiveVariables["out"], reader["out"], checks = self.allSceneChecks - { "sets" } )

if __name__ == "__main__":
unittest.main()
188 changes: 79 additions & 109 deletions src/GafferUSD/USDLayerWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,22 @@ struct Filters
PathMatcher prune;
PathMatcher deleteObject;
PathMatcher deleteAttributes;
ScenePlug::ScenePath localPath;

// Merges in filters from sibling locations.
void add( const Filters &other )
void mergeSibling( const Filters &sibling )
{
fullyPruned = fullyPruned && other.fullyPruned;
prune.addPaths( other.prune );
deleteAttributes.addPaths( other.deleteAttributes );
deleteObject.addPaths( other.deleteObject );
fullyPruned = fullyPruned && sibling.fullyPruned;
prune.addPaths( sibling.prune );
deleteAttributes.addPaths( sibling.deleteAttributes );
deleteObject.addPaths( sibling.deleteObject );
}

// Merges in filters from child locations.
void addWithPrefix( const Filters &other, const ScenePlug::ScenePath &prefix )
void mergeChildren( const Filters &children )
{
fullyPruned = fullyPruned && other.fullyPruned;
prune.addPaths( other.prune, prefix );
deleteAttributes.addPaths( other.deleteAttributes, prefix );
deleteObject.addPaths( other.deleteObject, prefix );
fullyPruned = fullyPruned && children.fullyPruned;
prune.addPaths( children.prune, localPath );
deleteAttributes.addPaths( children.deleteAttributes, localPath );
deleteObject.addPaths( children.deleteObject, localPath );
}

};
Expand All @@ -109,116 +108,88 @@ struct Filters
// - Objects with identical hashes will be included in `Filter::deleteObject`.
// - Attributes with identical hashes will be included in `Filter::deleteAttributes`.
// - Subtrees which are identical in all properties will be included in `Filter::prune`.
//
/// \todo The core logic of this could be lifted into a `SceneAlgo::parallelReduceLocations()`
/// and reused elsewhere. The recursive way we're building PathMatchers by prefixing with the
/// parent as unwind might outperform other approaches we're using elsewhere.
Filters filtersWalk( const GafferScene::ScenePlug *baseScene, const GafferScene::ScenePlug *layerScene, const std::vector<float> &frames, const ScenePlug::ScenePath &path, const Gaffer::ThreadState &threadState, tbb::task_group_context &taskGroupContext )
Filters buildFilters( const GafferScene::ScenePlug *baseScene, const GafferScene::ScenePlug *layerScene, const std::vector<float> &frames )
{
ScenePlug::PathScope pathScope( threadState, &path );

bool attributesMatch = true;
bool objectsMatch = true;
bool canPrune = true;

for( auto frame : frames )
{
pathScope.setFrame( frame );
Context::EditableScope childNamesScope( Context::current() );

if( !layerScene->existsPlug()->getValue() )
{
return { false, g_emptyPathMatcher, g_emptyPathMatcher, g_emptyPathMatcher };
}
// We'll evaluate the childNames at shutter open, seems to be consistent with what we do elsewhere.
childNamesScope.setFrame( frames[0] );

if( attributesMatch )
return GafferScene::SceneAlgo::parallelReduceLocations(
baseScene,
Filters(),
[&] ( const ScenePlug *scene, const ScenePlug::ScenePath &path ) -> Filters
{
attributesMatch = baseScene->attributesPlug()->hash() == layerScene->attributesPlug()->hash();
canPrune = canPrune && attributesMatch;
}

if( objectsMatch )
{
objectsMatch = baseScene->objectPlug()->hash() == layerScene->objectPlug()->hash();
canPrune = canPrune && objectsMatch;
}

if( canPrune )
{
canPrune = baseScene->transformPlug()->hash() == layerScene->transformPlug()->hash();
}

if( canPrune )
{
canPrune = baseScene->boundPlug()->hash() == layerScene->boundPlug()->hash();
}
}

ScenePlug::ScenePath localPath;
if( path.size() )
{
// We only need the last part, because we prefix with
// the parent path as we unwind the recursion.
localPath.push_back( path.back() );
}

Filters result;
result.fullyPruned = canPrune;
if( attributesMatch )
{
result.deleteAttributes.addPath( localPath );
}
if( objectsMatch )
{
result.deleteObject.addPath( localPath );
}

IECore::ConstInternedStringVectorDataPtr baseChildNamesData = baseScene->childNamesPlug()->getValue();
const vector<InternedString> &baseChildNames = baseChildNamesData->readable();
if( !baseChildNames.empty() )
{
using ChildNameRange = tbb::blocked_range<std::vector<IECore::InternedString>::const_iterator>;
const ChildNameRange loopRange( baseChildNames.begin(), baseChildNames.end() );
bool attributesMatch = true;
bool objectsMatch = true;
bool canPrune = true;

Filters childFilters = tbb::parallel_reduce(

loopRange,

Filters(),

[&] ( const ChildNameRange &range, Filters x ) {
Context::EditableScope scope( Context::current() );
for( auto frame : frames )
{
scope.setFrame( frame );

ScenePlug::ScenePath childPath = path;
childPath.push_back( InternedString() );
for( const auto &childName : range )
if( !layerScene->existsPlug()->getValue() )
{
childPath.back() = childName;
const Filters childFilters = filtersWalk( baseScene, layerScene, frames, childPath, threadState, taskGroupContext );
x.add( childFilters );
return { false, g_emptyPathMatcher, g_emptyPathMatcher, g_emptyPathMatcher, ScenePlug::ScenePath() };
}
return x;
},

[] ( Filters x, const Filters &y ) {

x.add( y );
return x;

},
if( attributesMatch )
{
attributesMatch = baseScene->attributesPlug()->hash() == layerScene->attributesPlug()->hash();
canPrune = canPrune && attributesMatch;
}

taskGroupContext
if( objectsMatch )
{
objectsMatch = baseScene->objectPlug()->hash() == layerScene->objectPlug()->hash();
canPrune = canPrune && objectsMatch;
}

if( canPrune )
{
canPrune = baseScene->transformPlug()->hash() == layerScene->transformPlug()->hash();
}

);
if( canPrune )
{
canPrune = baseScene->boundPlug()->hash() == layerScene->boundPlug()->hash();
}
}

result.addWithPrefix( childFilters, localPath );
}
Filters result;
if( path.size() )
{
// We only need the last part, because we prefix with
// the parent path as we unwind the recursion.
result.localPath.push_back( path.back() );
}

if( result.fullyPruned )
{
result.prune.addPath( localPath );
}
result.fullyPruned = canPrune;
if( attributesMatch )
{
result.deleteAttributes.addPath( result.localPath );
}
if( objectsMatch )
{
result.deleteObject.addPath( result.localPath );
}

return result;
return result;
},
[]( Filters &result, const Filters &childrenResult )
{
result.mergeChildren( childrenResult );
if( result.fullyPruned )
{
result.prune.addPath( result.localPath );
}
},
[]( Filters &result, const Filters &siblingResult )
{
result.mergeSibling( siblingResult );
}
);
}

class ScopedDirectory : boost::noncopyable
Expand Down Expand Up @@ -547,8 +518,7 @@ void USDLayerWriter::executeSequence( const std::vector<float> &frames ) const

// Figure out the filters for our Prune, DeleteObject and DeleteAttribute
// nodes.
tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); // Prevents outer tasks silently cancelling our tasks
const Filters filters = filtersWalk( basePlug(), layerPlug(), frames, ScenePlug::ScenePath(), ThreadState::current(), taskGroupContext );
const Filters filters = buildFilters( basePlug(), layerPlug(), frames );

// Pass the filter settings via context variables since we can't call
// `Plug::setValue()` from `executeSequence()` because it would violate
Expand Down

0 comments on commit 4008b88

Please sign in to comment.