diff --git a/include/GafferScene/Capsule.h b/include/GafferScene/Capsule.h index 1cb625afbf5..0111f1b4b4f 100644 --- a/include/GafferScene/Capsule.h +++ b/include/GafferScene/Capsule.h @@ -90,7 +90,7 @@ class GAFFERSCENE_API Capsule : public IECoreScenePreview::Procedural const ScenePlug::ScenePath &root() const; const Gaffer::Context *context() const; - private : + protected : void throwIfNoScene() const; diff --git a/include/GafferScene/Instancer.h b/include/GafferScene/Instancer.h index 31512c232d5..08b283ed4eb 100644 --- a/include/GafferScene/Instancer.h +++ b/include/GafferScene/Instancer.h @@ -39,6 +39,7 @@ #include "GafferScene/Export.h" #include "GafferScene/BranchCreator.h" +#include "GafferScene/Capsule.h" namespace GafferScene { @@ -161,6 +162,28 @@ class GAFFERSCENE_API Instancer : public BranchCreator void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; + class InstancerCapsule : public Capsule + { + + public : + + InstancerCapsule(); + InstancerCapsule( + const ScenePlug *scene, + const ScenePlug::ScenePath &root, + const Gaffer::Context &context, + const IECore::MurmurHash &hash, + const Imath::Box3f &bound + ); + ~InstancerCapsule() override; + + IE_CORE_DECLAREEXTENSIONOBJECT( GafferScene::Instancer::InstancerCapsule, GafferScene::InstancerCapsuleTypeId, GafferScene::Capsule ); + + void render( IECoreScenePreview::Renderer *renderer ) const override; + }; + + IE_CORE_DECLAREPTR( InstancerCapsule ) + protected : void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; diff --git a/include/GafferScene/TypeIds.h b/include/GafferScene/TypeIds.h index 8700fe93e1f..b952e0392f5 100644 --- a/include/GafferScene/TypeIds.h +++ b/include/GafferScene/TypeIds.h @@ -176,6 +176,7 @@ enum TypeId MeshSplitTypeId = 110632, FramingConstraintTypeId = 110633, MeshNormalsTypeId = 110634, + InstancerCapsuleTypeId = 110635, PreviewPlaceholderTypeId = 110647, PreviewGeometryTypeId = 110648, diff --git a/src/GafferScene/Instancer.cpp b/src/GafferScene/Instancer.cpp index 7b45495cc23..b8a6b75a913 100644 --- a/src/GafferScene/Instancer.cpp +++ b/src/GafferScene/Instancer.cpp @@ -41,6 +41,8 @@ #include "GafferScene/SceneAlgo.h" #include "GafferScene/Private/ChildNamesMap.h" +#include "GafferScene/Private/RendererAlgo.h" +#include "GafferScene/Private/IECoreScenePreview/Renderer.h" #include "Gaffer/Context.h" #include "Gaffer/StringPlug.h" @@ -78,6 +80,8 @@ using namespace GafferScene; namespace { +const InternedString g_transformBlurOptionName( "option:render:transformBlur" ); + const PrimitiveVariable *findVertexVariable( const IECoreScene::Primitive* primitive, const InternedString &name ) { PrimitiveVariableMap::const_iterator it = primitive->variables.find( name ); @@ -286,6 +290,60 @@ int seedForPoint( int index, const PrimitiveVariable *primVar, int numSeeds, int InternedString g_prototypeRootName( "root" ); ConstInternedStringVectorDataPtr g_emptyNames = new InternedStringVectorData(); +// Copied from Cortex : src/IECoreScene/PrimitiveVariable.cpp +// +// Should probably be public, and probably named differently - it can be used to remap data +// with any set of indices, in this case, the indices are shorter than the original data, +// so "Expand" doesn't make sense. + +struct Expander +{ + + Expander( const std::vector &indices ) : m_indices( indices ) + { + } + + template + DataPtr operator()( const GeometricTypedData> *data ) const + { + typename GeometricTypedData>::Ptr result = expand( data ); + result->setInterpretation( data->getInterpretation() ); + return result; + } + + template + DataPtr operator()( const TypedData> *data ) const + { + return expand( data ); + } + + DataPtr operator()( const Data *data ) const + { + throw IECore::InvalidArgumentException( "Can only expand VectorTypedData" ); + } + + private : + + template + typename DataType::Ptr expand( const DataType *data ) const + { + const typename DataType::ValueType &compactValues = data->readable(); + + typename DataType::Ptr result = new DataType(); + typename DataType::ValueType &expandedValues = result->writable(); + expandedValues.reserve( m_indices.size() ); + for( const auto &index : m_indices ) + { + expandedValues.push_back( compactValues[index] ); + } + + return result; + } + + const std::vector &m_indices; + +}; + } ////////////////////////////////////////////////////////////////////////// @@ -377,13 +435,77 @@ class Instancer::EngineData : public Data } } - if( m_ids ) + if( m_numValidPrototypes ) { - for( size_t i = 0; i > instancesForPrototypeIndex( m_numPrototypes ); + if( m_indices ) { - // Iterate in reverse order so that in case of duplicates, the first one will override - size_t reverseI = numPoints() - 1 - i; - m_idsToPointIndices[(*m_ids)[reverseI]] = reverseI; + if( m_ids ) + { + for( size_t i = 0, e = numPoints(); i < e; ++i ) + { + int id = (*m_ids)[i]; + if( m_idsToPointIndices.try_emplace( id, i ).second ) + { + int prototypeIndex = m_prototypeIndexRemap[ (*m_indices)[ i ] % m_numPrototypes ]; + if( prototypeIndex != -1 ) + { + instancesForPrototypeIndex[ prototypeIndex ].push_back( i ); + } + } + } + } + else + { + for( size_t i = 0, e = numPoints(); i < e; ++i ) + { + int prototypeIndex = m_prototypeIndexRemap[ (*m_indices)[ i ] % m_numPrototypes ]; + if( prototypeIndex != -1 ) + { + instancesForPrototypeIndex[ prototypeIndex ].push_back( i ); + } + } + } + } + else + { + // TODO - shouldn't need to store this? ? ?? + + int prototypeIndex = m_prototypeIndexRemap[ 0 ]; + if( prototypeIndex != -1 ) // TODO - is checking for -1 necessary? + { + instancesForPrototypeIndex[ prototypeIndex ].reserve( numPoints() ); + if( m_ids ) + { + for( size_t i = 0; i outputChildNames = m_names->outputChildNames()->readable(); + for( unsigned int i = 0; i < m_numPrototypes; i++ ) + { + int prototypeIndex = m_prototypeIndexRemap[ i ]; + if( prototypeIndex == -1 ) + { + continue; + } + + m_instancesForPrototype.emplace( IECore::InternedString( outputChildNames[prototypeIndex] ), std::move( instancesForPrototypeIndex[prototypeIndex] ) ); } } @@ -405,6 +527,7 @@ class Instancer::EngineData : public Data } } + // TODO - we're being inconsistent about whether the time for this call is a concern in inner loops size_t numPoints() const { return m_primitive ? m_primitive->variableSize( PrimitiveVariable::Vertex ) : 0; @@ -415,14 +538,13 @@ class Instancer::EngineData : public Data return m_ids ? (*m_ids)[pointIndex] : pointIndex; } - size_t pointIndex( const InternedString &name ) const + size_t pointIndex( size_t i ) const { - const size_t i = boost::lexical_cast( name ); if( !m_ids ) { if( i >= numPoints() ) { - throw IECore::Exception( fmt::format( "Instance id \"{}\" is invalid, instancer produces only {} children. Topology may have changed during shutter.", name.string(), numPoints() ) ); + throw IECore::Exception( fmt::format( "Instance id \"{}\" is invalid, instancer produces only {} children. Topology may have changed during shutter.", i, numPoints() ) ); } return i; } @@ -430,12 +552,17 @@ class Instancer::EngineData : public Data IdsToPointIndices::const_iterator it = m_idsToPointIndices.find( i ); if( it == m_idsToPointIndices.end() ) { - throw IECore::Exception( fmt::format( "Instance id \"{}\" is invalid. Topology may have changed during shutter.", name.string() ) ); + throw IECore::Exception( fmt::format( "Instance id \"{}\" is invalid. Topology may have changed during shutter.", i ) ); } return it->second; } + size_t pointIndex( const InternedString &name ) const + { + return pointIndex( boost::lexical_cast( name ) ); + } + size_t numValidPrototypes() const { return m_numValidPrototypes; @@ -503,7 +630,17 @@ class Instancer::EngineData : public Data CompoundObject::ObjectMap &writableResult = result.members(); for( const auto &attributeCreator : m_attributeCreators ) { - writableResult[attributeCreator.first] = attributeCreator.second( pointIndex ); + writableResult[attributeCreator.name] = attributeCreator.func( pointIndex ); + } + } + + void instanceAttributeVectors( const std::vector &pointIndices, CompoundObject &result ) const + { + CompoundObject::ObjectMap &writableResult = result.members(); + for( const auto &attributeCreator : m_attributeCreators ) + { + writableResult[attributeCreator.name] = + IECore::dispatch( attributeCreator.data.get(), Expander( pointIndices ) ); } } @@ -590,6 +727,11 @@ class Instancer::EngineData : public Data } } + const std::vector & instancesForPrototype( const IECore::InternedString &prototypeName ) const + { + return m_instancesForPrototype.at( prototypeName ); + } + protected : // Needs to match setPrototypeContextVariables above, except that it operates on one @@ -637,24 +779,24 @@ class Instancer::EngineData : public Data private : - using AttributeCreator = std::function; + using AttributeCreatorFunc = std::function; - struct MakeAttributeCreator + struct MakeAttributeCreatorFunc { template - AttributeCreator operator()( const TypedData> *data ) + AttributeCreatorFunc operator()( const TypedData> *data ) { return std::bind( &createAttribute, data->readable(), ::_1 ); } template - AttributeCreator operator()( const GeometricTypedData> *data ) + AttributeCreatorFunc operator()( const GeometricTypedData> *data ) { return std::bind( &createGeometricAttribute, data->readable(), data->getInterpretation(), ::_1 ); } - AttributeCreator operator()( const Data *data ) + AttributeCreatorFunc operator()( const Data *data ) { throw IECore::InvalidArgumentException( "Expected VectorTypedData" ); } @@ -690,8 +832,8 @@ class Instancer::EngineData : public Data continue; } DataPtr d = primVar.second.expandedData(); - AttributeCreator attributeCreator = dispatch( d.get(), MakeAttributeCreator() ); - m_attributeCreators[attributePrefix + primVar.first] = attributeCreator; + AttributeCreatorFunc attributeCreator = dispatch( d.get(), MakeAttributeCreatorFunc() ); + m_attributeCreators.push_back( { attributePrefix + primVar.first, attributeCreator, d } ); m_attributesHash.append( primVar.first ); d->hash( m_attributesHash ); } @@ -804,8 +946,11 @@ class Instancer::EngineData : public Data } m_names = new Private::ChildNamesMap( inputNames ); + + const std::vector< InternedString > outputChildNames = m_names->outputChildNames()->readable(); m_numPrototypes = m_prototypeIndexRemap.size(); - m_numValidPrototypes = m_names->outputChildNames()->readable().size(); + m_numValidPrototypes = outputChildNames.size(); + } IECoreScene::ConstPrimitivePtr m_primitive; @@ -824,10 +969,19 @@ class Instancer::EngineData : public Data using IdsToPointIndices = std::unordered_map ; IdsToPointIndices m_idsToPointIndices; - boost::container::flat_map m_attributeCreators; + struct AttributeCreator + { + InternedString name; + AttributeCreatorFunc func; + DataPtr data; + }; + + std::vector< AttributeCreator > m_attributeCreators; MurmurHash m_attributesHash; const std::vector< PrototypeContextVariable > m_prototypeContextVariables; + + std::unordered_map< InternedString, std::vector > m_instancesForPrototype; }; ////////////////////////////////////////////////////////////////////////// @@ -918,7 +1072,6 @@ Instancer::Instancer( const std::string &name ) addChild( new ContextVariablePlug( "timeOffset", Plug::In, false, Plug::Flags::Default ) ); addChild( new AtomicCompoundDataPlug( "variations", Plug::Out, new CompoundData() ) ); addChild( new ObjectPlug( "__engine", Plug::Out, NullObject::defaultNullObject() ) ); - addChild( new AtomicCompoundDataPlug( "__prototypeChildNames", Plug::Out, new CompoundData ) ); addChild( new ScenePlug( "__capsuleScene", Plug::Out ) ); addChild( new PathMatcherDataPlug( "__setCollaborate", Plug::Out, new IECore::PathMatcherData() ) ); @@ -1157,34 +1310,24 @@ const Gaffer::ObjectPlug *Instancer::enginePlug() const return getChild( g_firstPlugIndex + 21 ); } -Gaffer::AtomicCompoundDataPlug *Instancer::prototypeChildNamesPlug() -{ - return getChild( g_firstPlugIndex + 22 ); -} - -const Gaffer::AtomicCompoundDataPlug *Instancer::prototypeChildNamesPlug() const -{ - return getChild( g_firstPlugIndex + 22 ); -} - GafferScene::ScenePlug *Instancer::capsuleScenePlug() { - return getChild( g_firstPlugIndex + 23 ); + return getChild( g_firstPlugIndex + 22 ); } const GafferScene::ScenePlug *Instancer::capsuleScenePlug() const { - return getChild( g_firstPlugIndex + 23 ); + return getChild( g_firstPlugIndex + 22 ); } Gaffer::PathMatcherDataPlug *Instancer::setCollaboratePlug() { - return getChild( g_firstPlugIndex + 24 ); + return getChild( g_firstPlugIndex + 23 ); } const Gaffer::PathMatcherDataPlug *Instancer::setCollaboratePlug() const { - return getChild( g_firstPlugIndex + 24 ); + return getChild( g_firstPlugIndex + 23 ); } void Instancer::affects( const Plug *input, AffectedPlugsContainer &outputs ) const @@ -1217,11 +1360,6 @@ void Instancer::affects( const Plug *input, AffectedPlugsContainer &outputs ) co outputs.push_back( enginePlug() ); } - if( input == enginePlug() ) - { - 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 @@ -1261,7 +1399,6 @@ void Instancer::affects( const Plug *input, AffectedPlugsContainer &outputs ) co if( input == enginePlug() || input == prototypesPlug()->setPlug() || - input == prototypeChildNamesPlug() || input == namePlug() ) { @@ -1312,10 +1449,6 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co timeOffsetPlug()->quantizePlug()->hash( h ); } } - else if( output == prototypeChildNamesPlug() ) - { - enginePlug()->hash( h ); - } else if( output == variationsPlug() ) { // The sum of the variations across different engines depends on all the engines, but @@ -1352,7 +1485,6 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co // accurate hash when we do actually have context variables: the slower hash won't change // if point locations change, unlike the engineHash which includes all changes engineHash( sourcePath, context, h ); - prototypeChildNamesHash( sourcePath, context, h ); Context::EditableScope scope( context ); scope.remove( ScenePlug::scenePathContextName ); prototypesPlug()->setPlug()->hash( h ); @@ -1360,17 +1492,15 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co return; } - IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( sourcePath, context ); - tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); for( const auto &prototypeName : engine->prototypeNames()->readable() ) { - const vector &childNames = prototypeChildNames->member( prototypeName )->readable(); + const std::vector &instancesForPrototype = engine->instancesForPrototype( prototypeName ); std::atomic h1Accum( 0 ), h2Accum( 0 ); const ThreadState &threadState = ThreadState::current(); - tbb::parallel_for( tbb::blocked_range( 0, childNames.size() ), [&]( const tbb::blocked_range &r ) + tbb::parallel_for( tbb::blocked_range( 0, instancesForPrototype.size() ), [&]( const tbb::blocked_range &r ) { Context::EditableScope scope( threadState ); // As part of the setCollaborate plug machinery, we put the sourcePath in the context. @@ -1378,10 +1508,11 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co scope.remove( ScenePlug::scenePathContextName ); for( size_t i = r.begin(); i != r.end(); ++i ) { - const size_t pointIndex = engine->pointIndex( childNames[i] ); + const size_t pointIndex = instancesForPrototype[i]; + size_t instanceId = engine->instanceId( pointIndex ); engine->setPrototypeContextVariables( pointIndex, scope ); IECore::MurmurHash instanceH; - instanceH.append( childNames[i] ); + instanceH.append( instanceId ); prototypesPlug()->setPlug()->hash( instanceH ); h1Accum += instanceH.h1(); h2Accum += instanceH.h2(); @@ -1400,8 +1531,7 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const { - // Both the enginePlug and prototypeChildNamesPlug are evaluated - // in a context in which scene:path holds the parent path for a + // EnginePlug is evaluated in a context in which scene:path holds the parent path for a // branch. if( output == enginePlug() ) { @@ -1523,52 +1653,6 @@ void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte ); return; } - else if( output == prototypeChildNamesPlug() ) - { - // Here we compute and cache the child names for all of - // the /instances/ locations at once. We - // could instead compute them one at a time in - // computeBranchChildNames() but that would require N - // passes over the input points, where N is the number - // of prototypes. - ConstEngineDataPtr engine = boost::static_pointer_cast( enginePlug()->getValue() ); - const auto &prototypeNames = engine->prototypeNames()->readable(); - - vector> indexedPrototypeChildIds; - - size_t numPrototypes = engine->numValidPrototypes(); - if( numPrototypes ) - { - indexedPrototypeChildIds.resize( numPrototypes ); - for( size_t i = 0, e = engine->numPoints(); i < e; ++i ) - { - int prototypeIndex = engine->prototypeIndex( i ); - if( prototypeIndex != -1 ) - { - indexedPrototypeChildIds[prototypeIndex].push_back( engine->instanceId( i ) ); - } - } - } - - CompoundDataPtr result = new CompoundData; - for( size_t i = 0; i < numPrototypes; ++i ) - { - // Sort and uniquify ids before converting to string - std::sort( indexedPrototypeChildIds[i].begin(), indexedPrototypeChildIds[i].end() ); - auto last = std::unique( indexedPrototypeChildIds[i].begin(), indexedPrototypeChildIds[i].end() ); - indexedPrototypeChildIds[i].erase( last, indexedPrototypeChildIds[i].end() ); - - InternedStringVectorDataPtr prototypeChildNames = new InternedStringVectorData; - for( size_t id : indexedPrototypeChildIds[i] ) - { - prototypeChildNames->writable().emplace_back( id ); - } - result->writable()[prototypeNames[i]] = prototypeChildNames; - } - - static_cast( output )->setValue( result ); - return; - } else if( output == variationsPlug() ) { // Compute the number of variations by accumulating massive lists of unique hashes from all EngineDatas @@ -1639,8 +1723,6 @@ void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte ConstEngineDataPtr engine = this->engine( sourcePath, context ); - IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( sourcePath, context ); - PathMatcherDataPtr outputSetData = new PathMatcherData; PathMatcher &outputSet = outputSetData->writable(); @@ -1653,13 +1735,13 @@ void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte branchPath.resize( 2 ); branchPath.back() = prototypeName; const ScenePlug::ScenePath *prototypeRoot = engine->prototypeRoot( prototypeName ); - - const vector &childNames = prototypeChildNames->member( prototypeName )->readable(); + + const std::vector &instancesForPrototype = engine->instancesForPrototype( prototypeName ); tbb::spin_mutex instanceMutex; branchPath.emplace_back( InternedString() ); const ThreadState &threadState = ThreadState::current(); - tbb::parallel_for( tbb::blocked_range( 0, childNames.size() ), [&]( const tbb::blocked_range &r ) + tbb::parallel_for( tbb::blocked_range( 0, instancesForPrototype.size() ), [&]( const tbb::blocked_range &r ) { Context::EditableScope scope( threadState ); // As part of the setCollaborate plug machinery, we put the sourcePath in the context. @@ -1668,13 +1750,14 @@ void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte for( size_t i = r.begin(); i != r.end(); ++i ) { - const size_t pointIndex = engine->pointIndex( childNames[i] ); + const size_t pointIndex = instancesForPrototype[i]; + size_t instanceId = engine->instanceId( pointIndex ); engine->setPrototypeContextVariables( pointIndex, scope ); ConstPathMatcherDataPtr instanceSet = prototypesPlug()->setPlug()->getValue(); PathMatcher pointInstanceSet = instanceSet->readable().subTree( *prototypeRoot ); tbb::spin_mutex::scoped_lock lock( instanceMutex ); - branchPath.back() = childNames[i]; + branchPath.back() = instanceId; outputSet.addPaths( pointInstanceSet, branchPath ); } }, @@ -1722,7 +1805,6 @@ bool Instancer::affectsBranchBound( const Gaffer::Plug *input ) const input == namePlug() || input == prototypesPlug()->boundPlug() || input == prototypesPlug()->transformPlug() || - input == prototypeChildNamesPlug() || input == outPlug()->childBoundsPlug() ; } @@ -1746,11 +1828,11 @@ void Instancer::hashBranchBound( const ScenePath &sourcePath, const ScenePath &b BranchCreator::hashBranchBound( sourcePath, branchPath, context, h ); engineHash( sourcePath, context, h ); - prototypeChildNamesHash( sourcePath, context, h ); h.append( branchPath.back() ); { PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); + prototypesPlug()->transformPlug()->hash( h ); prototypesPlug()->boundPlug()->hash( h ); } @@ -1785,8 +1867,6 @@ Imath::Box3f Instancer::computeBranchBound( const ScenePath &sourcePath, const S // more efficiently than `ScenePlug::childBounds()`. ConstEngineDataPtr e = engine( sourcePath, context ); - ConstCompoundDataPtr ic = prototypeChildNames( sourcePath, context ); - const vector &childNames = ic->member( branchPath.back() )->readable(); M44f childTransform; Box3f childBound; @@ -1796,17 +1876,21 @@ Imath::Box3f Instancer::computeBranchBound( const ScenePath &sourcePath, const S childBound = prototypesPlug()->boundPlug()->getValue(); } - using ISIterator = vector::const_iterator; + using ISIterator = vector::const_iterator; using ISRange = blocked_range; + const std::vector &instancesForPrototype = e->instancesForPrototype( branchPath.back() ); + + // TODO - might be worth using a looser approximation - expand point cloud bound by largest diagonal of + // prototype bound x largest scale task_group_context taskGroupContext( task_group_context::isolated ); return parallel_reduce( - ISRange( childNames.begin(), childNames.end() ), + ISRange( instancesForPrototype.begin(), instancesForPrototype.end() ), Box3f(), [ &e, &childBound, &childTransform ] ( const ISRange &r, Box3f u ) { for( ISIterator i = r.begin(); i != r.end(); ++i ) { - const size_t pointIndex = e->pointIndex( *i ); + const size_t pointIndex = *i; const M44f m = childTransform * e->instanceTransform( pointIndex ); const Box3f b = transform( childBound, m ); u.extendBy( b ); @@ -2045,14 +2129,13 @@ IECore::ConstObjectPtr Instancer::computeObject( const ScenePath &path, const Ga parentAndBranchPaths( path, sourcePath, branchPath ); if( branchPath.size() == 2 ) { - return new Capsule( + return new InstancerCapsule( capsuleScenePlug(), context->get( ScenePlug::scenePathContextName ) , *context, outPlug()->objectPlug()->hash(), outPlug()->boundPlug()->getValue() ); - } } @@ -2064,7 +2147,6 @@ bool Instancer::affectsBranchChildNames( const Gaffer::Plug *input ) const { return input == namePlug() || - input == prototypeChildNamesPlug() || input == enginePlug() ; } @@ -2087,7 +2169,7 @@ void Instancer::hashBranchChildNames( const ScenePath &sourcePath, const ScenePa { // "/instances/" BranchCreator::hashBranchChildNames( sourcePath, branchPath, context, h ); - prototypeChildNamesHash( sourcePath, context, h ); + engineHash( sourcePath, context, h ); h.append( branchPath.back() ); } else @@ -2119,9 +2201,31 @@ IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchChildNames( con } else if( branchPath.size() == 2 ) { - // "/instances/" - IECore::ConstCompoundDataPtr ic = prototypeChildNames( sourcePath, context ); - return ic->member( branchPath.back() ); + ConstEngineDataPtr engineData = engine( sourcePath, context ); + + const std::vector &instancesForPrototype = engineData->instancesForPrototype( branchPath.back() ); + + //std::vector indexedPrototypeChildIds = instancesForPrototype; + std::vector indexedPrototypeChildIds; + indexedPrototypeChildIds.reserve( instancesForPrototype.size() ); + + for( int q : instancesForPrototype ) + { + indexedPrototypeChildIds.push_back( engineData->instanceId( q ) ); + } + + // Sort and uniquify ids before converting to string + std::sort( indexedPrototypeChildIds.begin(), indexedPrototypeChildIds.end() ); + + InternedStringVectorDataPtr childNamesData = new InternedStringVectorData; + std::vector &childNames = childNamesData->writable(); + childNames.reserve( indexedPrototypeChildIds.size() ); + for( size_t id : indexedPrototypeChildIds ) + { + childNames.emplace_back( id ); + } + + return childNamesData; } else { @@ -2184,7 +2288,6 @@ bool Instancer::affectsBranchSet( const Gaffer::Plug *input ) const return input == enginePlug() || input == prototypesPlug()->setPlug() || - input == prototypeChildNamesPlug() || input == namePlug() || input == setCollaboratePlug() ; @@ -2221,7 +2324,6 @@ void Instancer::hashBranchSet( const ScenePath &sourcePath, const IECore::Intern else { engineHash( sourcePath, context, h ); - prototypeChildNamesHash( sourcePath, context, h ); prototypesPlug()->setPlug()->hash( h ); namePlug()->hash( h ); } @@ -2242,27 +2344,22 @@ IECore::ConstPathMatcherDataPtr Instancer::computeBranchSet( const ScenePath &so return setCollaboratePlug()->getValue(); } - IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( sourcePath, context ); ConstPathMatcherDataPtr inputSet = prototypesPlug()->setPlug()->getValue(); PathMatcherDataPtr outputSetData = new PathMatcherData; PathMatcher &outputSet = outputSetData->writable(); - vector branchPath( { namePlug()->getValue() } ); + vector branchPath( { namePlug()->getValue(), InternedString(), InternedString() } ); for( const auto &prototypeName : engine->prototypeNames()->readable() ) { - branchPath.resize( 2 ); - branchPath.back() = prototypeName; - PathMatcher instanceSet = inputSet->readable().subTree( *engine->prototypeRoot( prototypeName ) ); + branchPath[1] = prototypeName; - const vector &childNames = prototypeChildNames->member( prototypeName )->readable(); - - branchPath.emplace_back( InternedString() ); - for( const auto &childName : childNames ) + const std::vector &instancesForPrototype = engine->instancesForPrototype( prototypeName ); + for( int i : instancesForPrototype ) { - branchPath.back() = childName; + branchPath[2] = engine->instanceId( i ); outputSet.addPaths( instanceSet, branchPath ); } } @@ -2303,18 +2400,6 @@ void Instancer::engineHash( const ScenePath &sourcePath, const Gaffer::Context * enginePlug()->hash( h ); } -IECore::ConstCompoundDataPtr Instancer::prototypeChildNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const -{ - ScenePlug::PathScope scope( context, &sourcePath ); - return prototypeChildNamesPlug()->getValue(); -} - -void Instancer::prototypeChildNamesHash( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const -{ - ScenePlug::PathScope scope( context, &sourcePath ); - prototypeChildNamesPlug()->hash( h ); -} - Instancer::PrototypeScope::PrototypeScope( const Gaffer::ObjectPlug *enginePlug, const Gaffer::Context *context, const ScenePath *sourcePath, const ScenePath *branchPath ) : Gaffer::Context::EditableScope( context ) { @@ -2356,3 +2441,262 @@ void Instancer::PrototypeScope::setPrototype( const EngineData *engine, const Sc set( ScenePlug::scenePathContextName, prototypeRoot ); } } + +IE_CORE_DEFINEOBJECTTYPEDESCRIPTION( Instancer::InstancerCapsule ); + +Instancer::InstancerCapsule::InstancerCapsule() +{ +} + +Instancer::InstancerCapsule::InstancerCapsule( + const ScenePlug *scene, + const ScenePlug::ScenePath &root, + const Gaffer::Context &context, + const IECore::MurmurHash &hash, + const Imath::Box3f &bound +) + : Capsule( scene, root, context, hash, bound ) +{ +} + +Instancer::InstancerCapsule::~InstancerCapsule() +{ +} + +bool Instancer::InstancerCapsule::isEqualTo( const IECore::Object *other ) const +{ + const Instancer::InstancerCapsule *instancerCapsule = static_cast( other ); + if( !instancerCapsule ) + { + return false; + } + + return Capsule::isEqualTo( other ); +} + +void Instancer::InstancerCapsule::hash( IECore::MurmurHash &h ) const +{ + Capsule::hash( h ); + // TODO - check if procedural hash includes type id +} + +void Instancer::InstancerCapsule::copyFrom( const IECore::Object *other, IECore::Object::CopyContext *context ) +{ + Capsule::copyFrom( other, context ); +} + +void Instancer::InstancerCapsule::save( IECore::Object::SaveContext *context ) const +{ + Capsule::save( context ); +} + +void Instancer::InstancerCapsule::load( IECore::Object::LoadContextPtr context ) +{ + Capsule::load( context ); +} + +void Instancer::InstancerCapsule::memoryUsage( IECore::Object::MemoryAccumulator &accumulator ) const +{ + Capsule::memoryUsage( accumulator ); +} + + +void Instancer::InstancerCapsule::render( IECoreScenePreview::Renderer *renderer ) const +{ + // If we stick with this approach in the long run, this should become some sort of registry or query + // that can be set up by the renderer. + bool rendererSupportsInstancerCommand = renderer->name() == "Arnold"; + throwIfNoScene(); + const Instancer *instancerNode = IECore::runTimeCast( m_scene->node() ); + if( !instancerNode ) + { + throw IECore::Exception( "InstancerCapsule created from plug that does not belong to Instancer : " + m_scene->fullName() ); + } + + Context::EditableScope scope( m_context.get() ); + + ScenePlug::ScenePath enginePath( m_root.begin(), m_root.begin() + m_root.size() - 2 ); + + IECore::ConstCompoundObjectPtr globals = instancerNode->inPlug()->globals(); + ConstCompoundObjectPtr sceneAttributes = instancerNode->inPlug()->attributes( enginePath ); + + FloatVectorDataPtr sampleTimesData = new FloatVectorData; + vector &sampleTimes = sampleTimesData->writable(); + + const BoolData *transformBlurData = globals->member( g_transformBlurOptionName ); + bool transformBlur = transformBlurData ? transformBlurData->readable() : false; + V2f shutter = SceneAlgo::shutter( globals.get(), instancerNode->inPlug() ); + GafferScene::Private::RendererAlgo::transformMotionTimes( transformBlur, shutter, sceneAttributes.get(), sampleTimes ); + + if( sampleTimes.size() == 0 ) + { + sampleTimes.push_back( m_context->getFrame() ); + } + + std::vector< ConstEngineDataPtr > engines( sampleTimes.size() ); + for( unsigned int i = 0; i < sampleTimes.size(); i++ ) + { + scope.setFrame( sampleTimes[i] ); + engines[i] = instancerNode->engine( enginePath, scope.context() ); + } + + const std::vector &instancesForPrototype = engines[0]->instancesForPrototype( m_root.back() ); + const ScenePlug::ScenePath *prototypeRoot = engines[0]->prototypeRoot( m_root.back() ); + + bool hasAttributes = engines[0]->numInstanceAttributes() > 0; + scope.set( ScenePlug::scenePathContextName, prototypeRoot ); + + IECoreScenePreview::Renderer::AttributesInterfacePtr attribs; + if( !hasAttributes ) + { + attribs = renderer->attributes( sceneAttributes.get() ); + } + + const ScenePlug *prototypesPlug = instancerNode->prototypesPlug(); + std::string name; // TODO - does this require a full prefix? + + ObjectVectorPtr prototypesData = new ObjectVector(); + auto &prototypes = prototypesData->members(); + + ObjectVectorPtr prototypeAttributesData = new ObjectVector(); + auto &prototypeAttributes = prototypeAttributesData->members(); + + ConstObjectPtr capsule; + + vector pointTransforms( sampleTimes.size() ); + vector prototypeTransforms( sampleTimes.size() ); + + bool hasContexts = engines[0]->hasContextVariables(); + if( !hasContexts ) + { + IECore::MurmurHash h = m_hash; + h.append( 0 ); + + prototypes.push_back( new Capsule( + prototypesPlug, + *prototypeRoot, + *scope.context(), + h, + prototypesPlug->bound( *prototypeRoot ) // TODO - are there situations where this will be slow, and the renderer doesn't use it? + ) ); + + for( unsigned int i = 0; i < sampleTimes.size(); i++ ) + { + scope.setFrame( sampleTimes[i] ); + prototypeTransforms[i] = prototypesPlug->transform( *prototypeRoot ); + } + + prototypeAttributes.push_back( const_cast< IECore::CompoundObject*>( prototypesPlug->attributes( *prototypeRoot ).get() ) ); + } + + if( rendererSupportsInstancerCommand ) + { + ObjectVectorPtr instanceMatrixData = new ObjectVector(); + std::vector< std::vector * > instanceMatrix; + + for( unsigned int i = 0; i < sampleTimes.size(); i++ ) + { + M44fVectorDataPtr motionKey = new M44fVectorData(); + instanceMatrixData->members().push_back( motionKey ); + instanceMatrix.push_back( &motionKey->writable() ); + } + + std::vector instanceIdsForPrototype; + instanceIdsForPrototype.reserve( instancesForPrototype.size() ); + for( int i : instancesForPrototype ) + { + instanceIdsForPrototype.push_back( engines[0]->instanceId( i ) ); + } + + // TODO - for sample[0] this is a duplicate of instancesForPrototype + std::vector< std::vector< int > > pointIndices( sampleTimes.size() ); + + for( unsigned int i = 0; i < sampleTimes.size(); i++ ) + { + pointIndices[i].reserve( sampleTimes.size() ); + for( int instanceId : instanceIdsForPrototype ) + { + pointIndices[i].push_back( engines[i]->pointIndex( instanceId ) ); + } + } + + for( unsigned int i = 0; i < sampleTimes.size(); i++ ) + { + instanceMatrix[i]->reserve( instancesForPrototype.size() ); + for( int pointIndex : pointIndices[i] ) + { + instanceMatrix[i]->push_back( prototypeTransforms[i] * engines[i]->instanceTransform( pointIndex ) ); + } + } + + CompoundObjectPtr instanceAttributes = new CompoundObject(); + engines[0]->instanceAttributeVectors( pointIndices[0], *instanceAttributes ); + + IECore::CompoundObjectPtr instancerArguments = new IECore::CompoundObject(); + instancerArguments->members()["prototypes"] = prototypesData; + instancerArguments->members()["prototypeAttributes"] = prototypeAttributesData; + instancerArguments->members()["sampleTimes"] = sampleTimesData; + instancerArguments->members()["instanceMatrix"] = instanceMatrixData; + instancerArguments->members()["instanceAttributes"] = instanceAttributes; + + IECore::CompoundDataMap instancerArgumentsPacked; + instancerArgumentsPacked["argumentPointer"] = new UInt64Data( (size_t)( instancerArguments.get() ) ); + + renderer->command( "instancer", instancerArgumentsPacked ); + return; + } + + for( int pointIndex : instancesForPrototype ) + { + int instanceId = engines[0]->instanceId( pointIndex ); + if( hasAttributes ) + { + CompoundObjectPtr currentAttributes = sceneAttributes->copy(); + engines[0]->instanceAttributes( pointIndex, *currentAttributes ); + + attribs = renderer->attributes( currentAttributes.get() ); + } + + // TODO - does the name given to the renderer matter? + name.resize( std::numeric_limits< int >::digits10 + 1 ); + name.resize( std::to_chars( &name[0], &(*name.end()), instanceId ).ptr - &name[0] ); + + if( hasContexts ) + { + throw IECore::Exception( "We don't like contexts yet" ); + + engines[0]->setPrototypeContextVariables( pointIndex, scope ); + + IECore::MurmurHash h = m_hash; + h.append( 0 ); + + /*capsule = new Capsule( + prototypesPlug, + *prototypeRoot, + *scope.context(), + h, // TODO - is hash being used by instancer command? + prototypesPlug->bound( *prototypeRoot ) // TODO - is bound being used? Is it fast enough to not care? + );*/ + } + + IECoreScenePreview::Renderer::ObjectInterfacePtr objectInterface = renderer->object( name, prototypes[0].get(), attribs.get() ); + + + if( sampleTimes.size() <= 1 ) + { + objectInterface->transform( prototypeTransforms[0] * engines[0]->instanceTransform( pointIndex ) ); + } + else + { + // TODO - special case for when the transforms aren't different between samples? + for( unsigned int i = 0; i < engines.size(); i++ ) + { + int curPointIndex = i == 0 ? pointIndex : engines[i]->pointIndex( instanceId ); + pointTransforms[i] = prototypeTransforms[i] * engines[i]->instanceTransform( curPointIndex ); + } + + objectInterface->transform( pointTransforms, sampleTimes ); + } + + } +} diff --git a/src/GafferSceneModule/HierarchyBinding.cpp b/src/GafferSceneModule/HierarchyBinding.cpp index f0c18077292..1eef14df824 100644 --- a/src/GafferSceneModule/HierarchyBinding.cpp +++ b/src/GafferSceneModule/HierarchyBinding.cpp @@ -151,6 +151,23 @@ void GafferSceneModule::bindHierarchy() .attr( "__qualname__" ) = "Instancer.ContextVariablePlug" ; + // TODO - figure out why this isn't working + /*IECorePython::RunTimeTypedClass() + .def( + init< + const Instancer::ConstEngineDataPtr &, + const ScenePlug *, + const ScenePlug::ScenePath &, + const Gaffer::Context &, + const IECore::MurmurHash &, + const Imath::Box3f & + >() + ) + //.def( "scene", &scene ) + //.def( "root", &root ) + //.def( "context", &context ) + ;*/ + } { diff --git a/src/IECoreArnold/Renderer.cpp b/src/IECoreArnold/Renderer.cpp index 905d00de660..408de26baaa 100644 --- a/src/IECoreArnold/Renderer.cpp +++ b/src/IECoreArnold/Renderer.cpp @@ -42,6 +42,7 @@ #include "IECoreArnold/ParameterAlgo.h" #include "IECoreArnold/ShaderNetworkAlgo.h" #include "IECoreArnold/UniverseBlock.h" +#include "GafferScene/Instancer.h" #include "IECoreScene/Camera.h" #include "IECoreScene/CurvesPrimitive.h" @@ -449,7 +450,7 @@ class ArnoldRendererBase : public IECoreScenePreview::Renderer IECore::MessageHandlerPtr m_messageHandler; - private : + protected : AtNode *m_parentNode; @@ -2029,7 +2030,7 @@ class Instance public : - AtNode *node() + AtNode *node() const { return m_ginstance.get() ? m_ginstance.get() : m_node.get(); } @@ -2084,6 +2085,7 @@ class Instance // Forward declaration AtNode *convertProcedural( IECoreScenePreview::ConstProceduralPtr procedural, const ArnoldAttributes *attributes, AtUniverse *universe, const std::string &nodeName, AtNode *parentNode ); + class InstanceCache : public IECore::RefCounted { @@ -2878,6 +2880,80 @@ class ArnoldObject : public ArnoldObjectBase IE_CORE_DECLAREPTR( ArnoldLight ) +const AtString g_instancerArnoldString("instancer"); +const AtString g_instanceMatrixArnoldString("instance_matrix"); +const AtString g_instanceMotionStartArnoldString("instance_motion_start"); +const AtString g_instanceMotionEndArnoldString("instance_motion_end"); +const AtString g_instanceVisibilityArnoldString("instance_visibility"); +const AtString g_nodesArnoldString("nodes"); + +void setupInstancer( ArnoldRendererBase *renderer, AtNode *result, + const std::vector &prototypes, const std::vector &prototypeAttributes, + const std::vector &sampleTimes, const std::vector< const std::vector< Imath::M44f > * >& transforms, + const IECore::CompoundObject::ObjectMap &instanceAttributes +) +{ + std::vector prototypeVisibilities( prototypes.size() ); + + // TODO - exception handling + std::vector arnoldPrototypes( prototypes.size() ); + for( unsigned int i = 0; i < prototypes.size(); i++ ) + { + std::string name = "prototype" + std::to_string( i ); // TODO + + IECore::CompoundObjectPtr attributesWithOverride = IECore::runTimeCast( prototypeAttributes[i].get() )->copy(); + attributesWithOverride->members()[ g_automaticInstancingAttributeName ] = new IECore::BoolData( false ); + + IECoreScenePreview::Renderer::AttributesInterfacePtr attributes = renderer->attributes( attributesWithOverride.get() ); + ArnoldRendererBase::ObjectInterfacePtr foo = renderer->object( name, prototypes[i].get(), attributes.get() ); + + arnoldPrototypes[i] = static_cast< ArnoldObjectBase *>( foo.get() )->instance().node(); + + prototypeVisibilities[i] = AiNodeGetByte( arnoldPrototypes[i], g_visibilityArnoldString ); + AiNodeSetByte( arnoldPrototypes[i], g_visibilityArnoldString, 0 ); + + } + + AiNodeSetArray( result, g_nodesArnoldString, AiArrayConvert( prototypes.size(), 1, AI_TYPE_NODE, &arnoldPrototypes[0] ) ); + + AtArray *matrices = AiArrayAllocate( transforms[0]->size(), transforms.size(), AI_TYPE_MATRIX ); + for( unsigned int i = 0; i < transforms.size(); i++ ) + { + AiArraySetKey( matrices, i, &( (*transforms[i])[0] ) ); + } + + AiNodeSetArray( result, g_instanceMatrixArnoldString, matrices ); + + AiNodeSetArray( result, g_instanceVisibilityArnoldString, AiArrayConvert( prototypeVisibilities.size(), 1, AI_TYPE_BYTE, &prototypeVisibilities[0] ) ); + + for( const auto &i : instanceAttributes ) + { + bool array; + int arnoldType = ParameterAlgo::parameterType( i.second->typeId(), array ); + const IECore::Data *attributeData = IECore::runTimeCast< IECore::Data >( i.second.get() ); + + if( arnoldType == AI_TYPE_NONE || !array || !attributeData ) + { + IECore::msg( IECore::Msg::Warning, "ArnoldRenderer", "Instance attributes: " + i.first.string() + " : Unsupported type " + i.second->typeName() ); + continue; + } + + std::string testStr = std::string("constant ARRAY ") + AiParamGetTypeName( arnoldType ); + + AtString name( ( "instance_" + i.first.string() ).c_str() ); + AiNodeDeclare( result, name, (std::string("constant ARRAY ") + AiParamGetTypeName( arnoldType )).c_str() ); + AiNodeSetArray( result, name, ParameterAlgo::dataToArray( attributeData, arnoldType ) ); + } + + + AiNodeDeclare( result, g_instanceMotionStartArnoldString, "constant ARRAY FLOAT" ); + AiNodeDeclare( result, g_instanceMotionEndArnoldString, "constant ARRAY FLOAT" ); + AiNodeSetArray( result, g_instanceMotionStartArnoldString, AiArrayConvert( 1, 1, AI_TYPE_FLOAT, &sampleTimes.front() ) ); + AiNodeSetArray( result, g_instanceMotionEndArnoldString, AiArrayConvert( 1, 1, AI_TYPE_FLOAT, &sampleTimes.back() ) ); +} + + + } // namespace ////////////////////////////////////////////////////////////////////////// @@ -3012,6 +3088,53 @@ class ProceduralRenderer final : public ArnoldRendererBase m_shaderCache->nodesCreated( nodes ); } + IECore::DataPtr command( const IECore::InternedString name, const IECore::CompoundDataMap ¶meters ) override + { + if( name == "instancer" ) + { + const IECore::CompoundObject *arguments = (const IECore::CompoundObject*)( + parameter( parameters, "argumentPointer", 0 ) + ); + + if( !arguments ) + { + throw IECore::Exception( "Arnold Renderer : Invalid instancer arguments" ); + } + + const IECore::ObjectVector *prototypesData = arguments->member( "prototypes", true ); + const IECore::ObjectVector *prototypeAttributesData = arguments->member( "prototypeAttributes", true ); + const IECore::FloatVectorData *sampleTimesData = arguments->member( "sampleTimes", true ); + const IECore::ObjectVector *instanceMatrixData = arguments->member( "instanceMatrix", true ); + const IECore::CompoundObject *instanceAttributesData = arguments->member( "instanceAttributes", true ); + + std::vector< const std::vector< Imath::M44f > * > instanceMatrix; + for( const auto &i : instanceMatrixData->members() ) + { + instanceMatrix.push_back( &IECore::runTimeCast< const IECore::M44fVectorData >( i.get() )->readable() ); + } + + AtString instancerName( "testNodeNameTODO" ); + AtNode *result = AiNode( m_universe, g_instancerArnoldString, instancerName, m_parentNode ); + + setupInstancer( this, result, + prototypesData->members(), prototypeAttributesData->members(), + sampleTimesData->readable(), instanceMatrix, instanceAttributesData->members() + ); + + NodesCreatedMutex::scoped_lock lock( m_nodesCreatedMutex ); + m_nodesCreated.push_back( result ); + + return nullptr; + } + else if( boost::starts_with( name.string(), "ai:" ) || name.string().find( ":" ) == string::npos ) + { + IECore::msg( IECore::Msg::Warning, "IECoreArnold::ProceduralRenderer::command", fmt::format( "Unknown command \"{}\".", name.c_str() ) ); + } + + return nullptr; + } + + private : IECore::ConstCompoundObjectPtr m_attributesToInherit;