Skip to content

Commit 56d4062

Browse files
authored
Merge pull request #1273 from johnhaddon/materialPurpose
USDScene : Support material purpose (both read and write)
2 parents 2e3e4ea + 61d5da9 commit 56d4062

File tree

5 files changed

+228
-117
lines changed

5 files changed

+228
-117
lines changed

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 109 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -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

606621
USDScene::~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

10231034
void 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 );

contrib/IECoreUSD/src/IECoreUSD/USDScene.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
#include "IECore/PathMatcherData.h"
4444

45+
#include "boost/container/flat_map.hpp"
4546
// Included here to avoid it being included indirectly via `stage.h`, inside the
4647
// scope of IECORE_PUSH_DEFAULT_VISIBILITY.
4748
#include "boost/function/function_base.hpp"
@@ -119,7 +120,13 @@ class USDScene : public IECoreScene::SceneInterface
119120
IOPtr m_root;
120121
LocationPtr m_location;
121122

122-
std::map< const IECore::InternedString, IECoreScene::ConstShaderNetworkPtr > m_shaders;
123+
// Contains all the shader networks for a single material, mapping from material output
124+
// (e.g. "surface", "displacement" etc) to the shading network that drives that output.
125+
using MaterialNetworks = boost::container::flat_map<pxr::TfToken, IECoreScene::ConstShaderNetworkPtr>;
126+
// Contains the materials to be bound for this location, indexed by purpose.
127+
using Materials = boost::container::flat_map<pxr::TfToken, MaterialNetworks>;
128+
Materials m_materials;
129+
123130
};
124131

125132
IE_CORE_DECLAREPTR( USDScene )

0 commit comments

Comments
 (0)