@@ -76,7 +76,6 @@ IECORE_POP_DEFAULT_VISIBILITY
7676#include " boost/algorithm/string/predicate.hpp"
7777#include " boost/algorithm/string/replace.hpp"
7878#include " boost/algorithm/string/split.hpp"
79- #include " boost/container/flat_map.hpp"
8079#include " boost/format.hpp"
8180#include " boost/functional/hash.hpp"
8281
@@ -358,46 +357,15 @@ SceneInterface::NameList setNamesInternal( const pxr::UsdPrim &prim, bool includ
358357 return result;
359358}
360359
361- void populateMaterial ( pxr::UsdShadeMaterial &mat, const std::map< const InternedString, ConstShaderNetworkPtr > &shaderTypes )
360+ void populateMaterial ( pxr::UsdShadeMaterial &mat, const boost::container::flat_map<pxr::TfToken, IECoreScene:: ConstShaderNetworkPtr> &shaders )
362361{
363- for ( auto &shaderType : shaderTypes )
362+ for ( const auto &[output, shaderNetwork] : shaders )
364363 {
365- std::string type = AttributeAlgo::nameToUSD ( shaderType.first .string () ).name .GetString ();
366- std::string prefix;
367- size_t colonPos = type.rfind ( " :" );
368- if ( colonPos != std::string::npos )
369- {
370- prefix = type.substr ( 0 , colonPos );
371- type = type.substr ( colonPos + 1 );
372- }
373-
374- pxr::UsdShadeOutput matOutput;
375- pxr::TfToken renderContext = prefix.size () ? pxr::TfToken ( prefix ) : pxr::UsdShadeTokens->universalRenderContext ;
376- if ( type == " surface" )
377- {
378- matOutput = mat.CreateSurfaceOutput ( renderContext );
379- }
380- else if ( type == " displacement" )
381- {
382- matOutput = mat.CreateDisplacementOutput ( renderContext );
383- }
384- else if ( type == " volume" )
385- {
386- matOutput = mat.CreateVolumeOutput ( renderContext );
387- }
388- else
389- {
390- IECore::msg (
391- IECore::Msg::Warning, " IECoreUSD::ShaderAlgo::writeShaderNetwork" ,
392- boost::format ( " Unrecognized shader type \" %1%\" " ) % type
393- );
394-
395- continue ;
396- }
364+ pxr::UsdShadeOutput matOutput = mat.CreateOutput ( output, pxr::SdfValueTypeNames->Token );
397365
398- std::string shaderContainerName = boost::replace_all_copy ( shaderType. first . string (), " :" , " _" ) + " _shaders" ;
366+ std::string shaderContainerName = boost::replace_all_copy ( output. GetString (), " :" , " _" ) + " _shaders" ;
399367 pxr::UsdGeomScope shaderContainer = pxr::UsdGeomScope::Define ( mat.GetPrim ().GetStage (), mat.GetPath ().AppendChild ( pxr::TfToken ( shaderContainerName ) ) );
400- pxr::UsdShadeOutput networkOut = ShaderAlgo::writeShaderNetwork ( shaderType. second .get (), shaderContainer.GetPrim () );
368+ pxr::UsdShadeOutput networkOut = ShaderAlgo::writeShaderNetwork ( shaderNetwork .get (), shaderContainer.GetPrim () );
401369
402370 if ( networkOut.GetPrim ().IsValid () )
403371 {
@@ -406,6 +374,28 @@ void populateMaterial( pxr::UsdShadeMaterial &mat, const std::map< const Interne
406374 }
407375}
408376
377+ std::tuple<pxr::TfToken, pxr::TfToken> materialOutputAndPurpose ( const std::string &attributeName )
378+ {
379+ for ( const auto &purpose : { pxr::UsdShadeTokens->preview , pxr::UsdShadeTokens->full } )
380+ {
381+ if (
382+ boost::ends_with ( attributeName, purpose.GetString () ) &&
383+ attributeName.size () > purpose.GetString ().size ()
384+ )
385+ {
386+ size_t colonIndex = attributeName.size () - purpose.GetString ().size () - 1 ;
387+ if ( attributeName[colonIndex] == ' :' )
388+ {
389+ return std::make_tuple (
390+ AttributeAlgo::nameToUSD ( attributeName.substr ( 0 , colonIndex ) ).name ,
391+ pxr::TfToken ( attributeName.substr ( colonIndex + 1 ) )
392+ );
393+ }
394+ }
395+ }
396+ return { AttributeAlgo::nameToUSD ( attributeName ).name , pxr::UsdShadeTokens->allPurpose };
397+ }
398+
409399// / SdfPath is the appropriate cache key for _storage_, but we need a
410400// / `UsdShadeOutput` for computation. This struct provides the implicit
411401// / conversion that LRUCache needs to make that possible.
@@ -468,6 +458,14 @@ class USDScene::IO : public RefCounted
468458 m_timeCodesPerSecond( m_stage->GetTimeCodesPerSecond () ),
469459 m_shaderNetworkCache( 10 * 1024 * 1024 ) // 10Mb
470460 {
461+ // Although the USD API implies otherwise, we need a different
462+ // cache per-purpose because `UsdShadeMaterialBindingAPI::ComputeBoundMaterial()`
463+ // gives inconsistent results if the cache is shared. We pre-populate
464+ // `m_usdBindingsCaches` here because it wouldn't be thread-safe to
465+ // make insertions in `computeBoundMaterial()`.
466+ m_usdBindingsCaches[pxr::UsdShadeTokens->allPurpose ];
467+ m_usdBindingsCaches[pxr::UsdShadeTokens->full ];
468+ m_usdBindingsCaches[pxr::UsdShadeTokens->preview ];
471469 }
472470
473471 ~IO () override
@@ -534,14 +532,31 @@ class USDScene::IO : public RefCounted
534532 return m_allTags;
535533 }
536534
537- pxr::UsdShadeMaterial computeBoundMaterial ( const pxr::UsdPrim &prim )
535+ // / \todo This "flattens" material assignment, so that materials assigned at `/root` are loaded as attributes
536+ // / on `/root/child` as well. This is not really what we want - we want to load sparsely and let attribute
537+ // / inheritance do the rest. This would be complicated by two factors :
538+ // /
539+ // / - USD's collection-based bindings. A collection-based binding on an ancestor prim would need to be transformed
540+ // / into a Cortex attribute on the prim, if the collection includes the prim.
541+ // / - USD's `bindingStrength` concept, where `UsdShadeTokens->strongerThanDescendants` allows an ancestor's
542+ // / binding to clobber descendant bindings during resolution. It is not clear how to represent that in Cortex -
543+ // / perhaps by not loading the descendant attributes at all?
544+ pxr::UsdShadeMaterial computeBoundMaterial ( const pxr::UsdPrim &prim, const pxr::TfToken &materialPurpose )
538545 {
539546 // This should be thread safe, despite using caches, because
540547 // BindingsCache and CollectionQueryCache are implemented by USD as
541548 // tbb::concurrent_unordered_map
542- return pxr::UsdShadeMaterialBindingAPI ( prim ).ComputeBoundMaterial (
543- &m_usdBindingsCache, &m_usdCollectionQueryCache
549+ pxr::UsdRelationship bindingRelationship;
550+ pxr::UsdShadeMaterial material = pxr::UsdShadeMaterialBindingAPI ( prim ).ComputeBoundMaterial (
551+ &m_usdBindingsCaches.at ( materialPurpose ), &m_usdCollectionQueryCache, materialPurpose, &bindingRelationship
544552 );
553+ if ( material && materialPurpose != pxr::UsdShadeTokens->allPurpose && bindingRelationship.GetBaseName () != materialPurpose )
554+ {
555+ // Ignore USD fallback to the all purpose binding. We want to load only the bindings that actually exist,
556+ // and then allow people to manage them after loading.
557+ return pxr::UsdShadeMaterial ();
558+ }
559+ return material;
545560 }
546561
547562 IECoreScene::ConstShaderNetworkPtr readShaderNetwork ( const pxr::UsdShadeOutput &output )
@@ -579,7 +594,7 @@ class USDScene::IO : public RefCounted
579594 std::once_flag m_allTagsFlag;
580595 SceneInterface::NameList m_allTags;
581596
582- pxr::UsdShadeMaterialBindingAPI::BindingsCache m_usdBindingsCache ;
597+ boost::container::flat_map< pxr::TfToken, pxr:: UsdShadeMaterialBindingAPI::BindingsCache> m_usdBindingsCaches ;
583598 pxr::UsdShadeMaterialBindingAPI::CollectionQueryCache m_usdCollectionQueryCache;
584599
585600 ShaderNetworkCache m_shaderNetworkCache;
@@ -605,7 +620,7 @@ USDScene::USDScene( IOPtr io, LocationPtr location )
605620
606621USDScene::~USDScene ()
607622{
608- if ( m_shaders .size () )
623+ if ( m_materials .size () )
609624 {
610625 try
611626 {
@@ -626,24 +641,29 @@ USDScene::~USDScene()
626641 materialContainer.GetPrim ().SetMetadata ( g_metadataAutoMaterials, true );
627642 }
628643
629- // Use a hash to identify the combination of shaders in this material
630- IECore::MurmurHash materialHash;
631- for ( auto &shaderType : m_shaders )
644+ for ( const auto &[purpose, material] : m_materials )
632645 {
633- materialHash.append ( shaderType.first );
634- materialHash.append ( shaderType.second ->Object ::hash () );
635- }
636- pxr::TfToken matName ( " material_" + materialHash.toString () );
646+ // Use a hash to identify the combination of shaders in this material.
647+ IECore::MurmurHash materialHash;
648+ for ( const auto &[output, shaderNetwork] : material )
649+ {
650+ materialHash.append ( output );
651+ materialHash.append ( shaderNetwork->Object ::hash () );
652+ }
653+ pxr::TfToken matName ( " material_" + materialHash.toString () );
637654
638- pxr::SdfPath matPath = materialContainer.GetPrim ().GetPath ().AppendChild ( matName );
639- pxr::UsdShadeMaterial mat = pxr::UsdShadeMaterial::Get ( materialContainer.GetPrim ().GetStage (), matPath );
640- if ( !mat )
641- {
642- // Another location has not yet defined this material
643- mat = pxr::UsdShadeMaterial::Define ( materialContainer.GetPrim ().GetStage (), matPath );
644- populateMaterial ( mat, m_shaders );
655+ // Write the material if it hasn't been written already.
656+ pxr::SdfPath matPath = materialContainer.GetPrim ().GetPath ().AppendChild ( matName );
657+ pxr::UsdShadeMaterial mat = pxr::UsdShadeMaterial::Get ( materialContainer.GetPrim ().GetStage (), matPath );
658+ if ( !mat )
659+ {
660+ mat = pxr::UsdShadeMaterial::Define ( materialContainer.GetPrim ().GetStage (), matPath );
661+ populateMaterial ( mat, material );
662+ }
663+
664+ // Bind the material to this location
665+ pxr::UsdShadeMaterialBindingAPI ( m_location->prim ).Bind ( mat, pxr::UsdShadeTokens->fallbackStrength , purpose );
645666 }
646- pxr::UsdShadeMaterialBindingAPI ( m_location->prim ).Bind ( mat );
647667 }
648668 catch ( std::exception &e )
649669 {
@@ -839,19 +859,14 @@ bool USDScene::hasAttribute( const SceneInterface::Name &name ) const
839859 }
840860 else
841861 {
842- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
843- pxr::UsdPrim matPrim = mat.GetPrim ();
844-
845- if ( matPrim.IsValid () )
862+ const auto &[output, purpose] = materialOutputAndPurpose ( name.string () );
863+ if ( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim , purpose ) )
846864 {
847- pxr::TfToken n = AttributeAlgo::nameToUSD ( name.string () ).name ;
848- pxr::UsdShadeOutput o = mat.GetOutput ( n );
849- if ( o && pxr::UsdAttribute ( o ).IsAuthored () )
865+ if ( pxr::UsdShadeOutput o = mat.GetOutput ( output ) )
850866 {
851- return true ;
867+ return o. GetAttr (). IsAuthored () ;
852868 }
853869 }
854-
855870 return false ;
856871 }
857872}
@@ -907,15 +922,18 @@ void USDScene::attributeNames( SceneInterface::NameList &attrs ) const
907922 }
908923 }
909924
910- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
911-
912- if ( mat.GetPrim ().IsValid () )
925+ for ( const auto &purpose : { pxr::UsdShadeTokens->allPurpose , pxr::UsdShadeTokens->preview , pxr::UsdShadeTokens->full } )
913926 {
914- for ( pxr::UsdShadeOutput &o : mat. GetOutputs ( ) )
927+ if ( pxr::UsdShadeMaterial mat = m_root-> computeBoundMaterial ( m_location-> prim , purpose ) )
915928 {
916- if ( o && pxr::UsdAttribute ( o ). IsAuthored ( ) )
929+ for ( pxr::UsdShadeOutput &o : mat. GetOutputs ( /* onlyAuthored = */ true ) )
917930 {
918- attrs.push_back ( AttributeAlgo::nameFromUSD ( { o.GetBaseName (), false } ) );
931+ InternedString attrName = AttributeAlgo::nameFromUSD ( { o.GetBaseName () , false } );
932+ if ( !purpose.IsEmpty () )
933+ {
934+ attrName = attrName.string () + " :" + purpose.GetString ();
935+ }
936+ attrs.push_back ( attrName );
919937 }
920938 }
921939 }
@@ -1000,24 +1018,17 @@ ConstObjectPtr USDScene::readAttribute( const SceneInterface::Name &name, double
10001018 }
10011019 else
10021020 {
1003- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
1004-
1005- if ( mat.GetPrim ().IsValid () )
1021+ const auto &[output, purpose] = materialOutputAndPurpose ( name.string () );
1022+ if ( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim , purpose ) )
10061023 {
1007- pxr::TfToken n = AttributeAlgo::nameToUSD ( name.string () ).name ;
1008-
1009- // If there's no output declared, then we will return nullptr, versus
1010- // having an output with no source connected, which will return an
1011- // empty shader network
1012- pxr::UsdShadeOutput o = mat.GetOutput ( n );
1013- if ( o && pxr::UsdAttribute ( o ).IsAuthored () )
1024+ pxr::UsdShadeOutput o = mat.GetOutput ( output );
1025+ if ( o && o.GetAttr ().IsAuthored () )
10141026 {
10151027 return m_root->readShaderNetwork ( o );
10161028 }
10171029 }
1030+ return nullptr ;
10181031 }
1019-
1020- return nullptr ;
10211032}
10221033
10231034void USDScene::writeAttribute ( const SceneInterface::Name &name, const Object *attribute, double time )
@@ -1079,7 +1090,8 @@ void USDScene::writeAttribute( const SceneInterface::Name &name, const Object *a
10791090 }
10801091 else if ( const IECoreScene::ShaderNetwork *shaderNetwork = runTimeCast<const ShaderNetwork>( attribute ) )
10811092 {
1082- m_shaders[name] = shaderNetwork;
1093+ const auto &[output, purpose] = materialOutputAndPurpose ( name.string () );
1094+ m_materials[purpose][output] = shaderNetwork;
10831095 }
10841096 else if ( name.string () == " gaffer:globals" )
10851097 {
@@ -1432,9 +1444,19 @@ void USDScene::attributesHash( double time, IECore::MurmurHash &h ) const
14321444 }
14331445 }
14341446
1435- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
1447+ bool haveMaterials = false ;
1448+ for ( const auto &purpose : { pxr::UsdShadeTokens->allPurpose , pxr::UsdShadeTokens->preview , pxr::UsdShadeTokens->full } )
1449+ {
1450+ if ( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim , purpose ) )
1451+ {
1452+ // \todo - This does not consider the possibility that the material could contain time-varying
1453+ // attributes
1454+ append ( mat.GetPrim ().GetPath (), h );
1455+ haveMaterials = true ;
1456+ }
1457+ }
14361458
1437- if ( haveAttributes || mat. GetPrim (). IsValid () )
1459+ if ( haveAttributes || haveMaterials )
14381460 {
14391461 h.append ( m_root->fileName () );
14401462
@@ -1446,13 +1468,6 @@ void USDScene::attributesHash( double time, IECore::MurmurHash &h ) const
14461468 appendPrimOrMasterPath ( m_location->prim , h );
14471469 }
14481470
1449- if ( mat.GetPrim ().IsValid () )
1450- {
1451- // \todo - This does not consider the possibility that the material could contain time-varying
1452- // attributes
1453- append ( mat.GetPrim ().GetPath (), h );
1454- }
1455-
14561471 if ( mightBeTimeVarying )
14571472 {
14581473 h.append ( time );
0 commit comments