@@ -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,22 @@ class USDScene::IO : public RefCounted
534532 return m_allTags;
535533 }
536534
537- pxr::UsdShadeMaterial computeBoundMaterial ( const pxr::UsdPrim &prim )
535+ pxr::UsdShadeMaterial computeBoundMaterial ( const pxr::UsdPrim &prim, const pxr::TfToken &materialPurpose )
538536 {
539537 // This should be thread safe, despite using caches, because
540538 // BindingsCache and CollectionQueryCache are implemented by USD as
541539 // tbb::concurrent_unordered_map
542- return pxr::UsdShadeMaterialBindingAPI ( prim ).ComputeBoundMaterial (
543- &m_usdBindingsCache, &m_usdCollectionQueryCache
540+ pxr::UsdRelationship bindingRelationship;
541+ pxr::UsdShadeMaterial material = pxr::UsdShadeMaterialBindingAPI ( prim ).ComputeBoundMaterial (
542+ &m_usdBindingsCaches.at ( materialPurpose ), &m_usdCollectionQueryCache, materialPurpose, &bindingRelationship
544543 );
544+ if ( material && materialPurpose != pxr::UsdShadeTokens->allPurpose && bindingRelationship.GetBaseName () != materialPurpose )
545+ {
546+ // Ignore USD fallback to the all purpose binding. We want to load only the bindings that actually exist,
547+ // and then allow people to manage them after loading.
548+ return pxr::UsdShadeMaterial ();
549+ }
550+ return material;
545551 }
546552
547553 IECoreScene::ConstShaderNetworkPtr readShaderNetwork ( const pxr::UsdShadeOutput &output )
@@ -579,7 +585,7 @@ class USDScene::IO : public RefCounted
579585 std::once_flag m_allTagsFlag;
580586 SceneInterface::NameList m_allTags;
581587
582- pxr::UsdShadeMaterialBindingAPI::BindingsCache m_usdBindingsCache ;
588+ boost::container::flat_map< pxr::TfToken, pxr:: UsdShadeMaterialBindingAPI::BindingsCache> m_usdBindingsCaches ;
583589 pxr::UsdShadeMaterialBindingAPI::CollectionQueryCache m_usdCollectionQueryCache;
584590
585591 ShaderNetworkCache m_shaderNetworkCache;
@@ -605,7 +611,7 @@ USDScene::USDScene( IOPtr io, LocationPtr location )
605611
606612USDScene::~USDScene ()
607613{
608- if ( m_shaders .size () )
614+ if ( m_materials .size () )
609615 {
610616 try
611617 {
@@ -626,24 +632,29 @@ USDScene::~USDScene()
626632 materialContainer.GetPrim ().SetMetadata ( g_metadataAutoMaterials, true );
627633 }
628634
629- // Use a hash to identify the combination of shaders in this material
630- IECore::MurmurHash materialHash;
631- for ( auto &shaderType : m_shaders )
635+ for ( const auto &[purpose, material] : m_materials )
632636 {
633- materialHash.append ( shaderType.first );
634- materialHash.append ( shaderType.second ->Object ::hash () );
635- }
636- pxr::TfToken matName ( " material_" + materialHash.toString () );
637+ // Use a hash to identify the combination of shaders in this material.
638+ IECore::MurmurHash materialHash;
639+ for ( const auto &[output, shaderNetwork] : material )
640+ {
641+ materialHash.append ( output );
642+ materialHash.append ( shaderNetwork->Object ::hash () );
643+ }
644+ pxr::TfToken matName ( " material_" + materialHash.toString () );
637645
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 );
646+ // Write the material if it hasn't been written already.
647+ pxr::SdfPath matPath = materialContainer.GetPrim ().GetPath ().AppendChild ( matName );
648+ pxr::UsdShadeMaterial mat = pxr::UsdShadeMaterial::Get ( materialContainer.GetPrim ().GetStage (), matPath );
649+ if ( !mat )
650+ {
651+ mat = pxr::UsdShadeMaterial::Define ( materialContainer.GetPrim ().GetStage (), matPath );
652+ populateMaterial ( mat, material );
653+ }
654+
655+ // Bind the material to this location
656+ pxr::UsdShadeMaterialBindingAPI ( m_location->prim ).Bind ( mat, pxr::UsdShadeTokens->fallbackStrength , purpose );
645657 }
646- pxr::UsdShadeMaterialBindingAPI ( m_location->prim ).Bind ( mat );
647658 }
648659 catch ( std::exception &e )
649660 {
@@ -839,19 +850,14 @@ bool USDScene::hasAttribute( const SceneInterface::Name &name ) const
839850 }
840851 else
841852 {
842- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
843- pxr::UsdPrim matPrim = mat.GetPrim ();
844-
845- if ( matPrim.IsValid () )
853+ const auto &[output, purpose] = materialOutputAndPurpose ( name.string () );
854+ if ( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim , purpose ) )
846855 {
847- pxr::TfToken n = AttributeAlgo::nameToUSD ( name.string () ).name ;
848- pxr::UsdShadeOutput o = mat.GetOutput ( n );
849- if ( o && pxr::UsdAttribute ( o ).IsAuthored () )
856+ if ( pxr::UsdShadeOutput o = mat.GetOutput ( output ) )
850857 {
851- return true ;
858+ return o. GetAttr (). IsAuthored () ;
852859 }
853860 }
854-
855861 return false ;
856862 }
857863}
@@ -907,15 +913,18 @@ void USDScene::attributeNames( SceneInterface::NameList &attrs ) const
907913 }
908914 }
909915
910- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
911-
912- if ( mat.GetPrim ().IsValid () )
916+ for ( const auto &purpose : { pxr::UsdShadeTokens->allPurpose , pxr::UsdShadeTokens->preview , pxr::UsdShadeTokens->full } )
913917 {
914- for ( pxr::UsdShadeOutput &o : mat. GetOutputs ( ) )
918+ if ( pxr::UsdShadeMaterial mat = m_root-> computeBoundMaterial ( m_location-> prim , purpose ) )
915919 {
916- if ( o && pxr::UsdAttribute ( o ). IsAuthored ( ) )
920+ for ( pxr::UsdShadeOutput &o : mat. GetOutputs ( /* onlyAuthored = */ true ) )
917921 {
918- attrs.push_back ( AttributeAlgo::nameFromUSD ( { o.GetBaseName (), false } ) );
922+ InternedString attrName = AttributeAlgo::nameFromUSD ( { o.GetBaseName () , false } );
923+ if ( !purpose.IsEmpty () )
924+ {
925+ attrName = attrName.string () + " :" + purpose.GetString ();
926+ }
927+ attrs.push_back ( attrName );
919928 }
920929 }
921930 }
@@ -1000,24 +1009,17 @@ ConstObjectPtr USDScene::readAttribute( const SceneInterface::Name &name, double
10001009 }
10011010 else
10021011 {
1003- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
1004-
1005- if ( mat.GetPrim ().IsValid () )
1012+ const auto &[output, purpose] = materialOutputAndPurpose ( name.string () );
1013+ if ( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim , purpose ) )
10061014 {
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 () )
1015+ pxr::UsdShadeOutput o = mat.GetOutput ( output );
1016+ if ( o && o.GetAttr ().IsAuthored () )
10141017 {
10151018 return m_root->readShaderNetwork ( o );
10161019 }
10171020 }
1021+ return nullptr ;
10181022 }
1019-
1020- return nullptr ;
10211023}
10221024
10231025void USDScene::writeAttribute ( const SceneInterface::Name &name, const Object *attribute, double time )
@@ -1079,7 +1081,8 @@ void USDScene::writeAttribute( const SceneInterface::Name &name, const Object *a
10791081 }
10801082 else if ( const IECoreScene::ShaderNetwork *shaderNetwork = runTimeCast<const ShaderNetwork>( attribute ) )
10811083 {
1082- m_shaders[name] = shaderNetwork;
1084+ const auto &[output, purpose] = materialOutputAndPurpose ( name.string () );
1085+ m_materials[purpose][output] = shaderNetwork;
10831086 }
10841087 else if ( name.string () == " gaffer:globals" )
10851088 {
@@ -1432,9 +1435,19 @@ void USDScene::attributesHash( double time, IECore::MurmurHash &h ) const
14321435 }
14331436 }
14341437
1435- pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim );
1438+ bool haveMaterials = false ;
1439+ for ( const auto &purpose : { pxr::UsdShadeTokens->allPurpose , pxr::UsdShadeTokens->preview , pxr::UsdShadeTokens->full } )
1440+ {
1441+ if ( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial ( m_location->prim , purpose ) )
1442+ {
1443+ // \todo - This does not consider the possibility that the material could contain time-varying
1444+ // attributes
1445+ append ( mat.GetPrim ().GetPath (), h );
1446+ haveMaterials = true ;
1447+ }
1448+ }
14361449
1437- if ( haveAttributes || mat. GetPrim (). IsValid () )
1450+ if ( haveAttributes || haveMaterials )
14381451 {
14391452 h.append ( m_root->fileName () );
14401453
@@ -1446,13 +1459,6 @@ void USDScene::attributesHash( double time, IECore::MurmurHash &h ) const
14461459 appendPrimOrMasterPath ( m_location->prim , h );
14471460 }
14481461
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-
14561462 if ( mightBeTimeVarying )
14571463 {
14581464 h.append ( time );
0 commit comments