Skip to content

Commit c05e5ad

Browse files
committed
Merge branch 'RB-10.4' into main
2 parents b1fe0a6 + 173e359 commit c05e5ad

File tree

4 files changed

+192
-14
lines changed

4 files changed

+192
-14
lines changed

Changes

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,18 @@ Breaking Changes
2929
- Primitive : Changed `variableIndexedView()` return type from `boost::optional` to `std::optional`.
3030
- MeshAlgo `distributePoints()` : Bug fixes mean subtle changes to the resulting points.
3131

32-
10.4.x.x (relative to 10.4.9.0)
32+
10.4.x.x (relative to 10.4.9.1)
3333
========
3434

35+
36+
10.4.9.1 (relative to 10.4.9.0)
37+
========
38+
39+
Fixes
40+
-----
41+
42+
- USDScene : Fixed hash clashes when the same USD file containing instanceable prims was opened a second time. Because USD doesn't assign prototypes deterministically, hashes would be shuffled between prototypes, yielding inconsistencies between the hashes for the two scenes. Hashes are now completely unique from scene-to-scene, so no locations can have clashing hashes.
43+
3544
10.4.9.0 (relative to 10.4.8.0)
3645
========
3746

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,14 @@ Imath::M44d localTransform( const pxr::UsdPrim &prim, pxr::UsdTimeCode time )
516516
return result;
517517
}
518518

519+
// Used to assign a unique hash to each USD file. Using a global counter rather than the file name
520+
// means that we treat the same file as separate if it is closed and reopened. This means it's not
521+
// a problem if USD changes things when a file is reopened. USD appears to not in general guarantee
522+
// that anything is the same when reopening an unchanged file - things we're aware of that could
523+
// cause problems without this conservative uniquifying are: how instance prototype names are
524+
// assigned, and hashes ( indices ) of SdfPaths
525+
std::atomic< int > g_usdFileCounter = 0;
526+
519527
} // namespace
520528

521529
class USDScene::Location : public RefCounted
@@ -539,7 +547,8 @@ class USDScene::IO : public RefCounted
539547
: m_fileName( fileName ), m_openMode( openMode ), m_stage( stage ),
540548
m_rootPrim( m_stage->GetPseudoRoot() ),
541549
m_timeCodesPerSecond( m_stage->GetTimeCodesPerSecond() ),
542-
m_shaderNetworkCache( 10 * 1024 * 1024 ) // 10Mb
550+
m_shaderNetworkCache( 10 * 1024 * 1024 ), // 10Mb
551+
m_uniqueId( g_usdFileCounter.fetch_add( 1, std::memory_order_relaxed ) )
543552
{
544553
// Although the USD API implies otherwise, we need a different
545554
// cache per-purpose because `UsdShadeMaterialBindingAPI::ComputeBoundMaterial()`
@@ -656,6 +665,11 @@ class USDScene::IO : public RefCounted
656665
return m_shaderNetworkCache.get( output );
657666
}
658667

668+
inline int uniqueId()
669+
{
670+
return m_uniqueId;
671+
}
672+
659673
private :
660674

661675
static pxr::UsdStageRefPtr makeStage( const std::string &fileName, IndexedIO::OpenMode openMode )
@@ -691,6 +705,10 @@ class USDScene::IO : public RefCounted
691705

692706
ShaderNetworkCache m_shaderNetworkCache;
693707

708+
// Used to identify a file uniquely ( including between different openings of the same filename,
709+
// since closing and reopening a file may cause USD to shuffle the contents ).
710+
const int m_uniqueId;
711+
694712
};
695713

696714
USDScene::USDScene( const std::string &fileName, IndexedIO::OpenMode openMode )
@@ -1307,7 +1325,7 @@ void USDScene::hashSet( const Name &name, IECore::MurmurHash &h ) const
13071325
{
13081326
SceneInterface::hashSet( name, h );
13091327

1310-
h.append( m_root->fileName() );
1328+
h.append( m_root->uniqueId() );
13111329
append( m_location->prim.GetPath(), h );
13121330
h.append( name );
13131331
}
@@ -1463,7 +1481,7 @@ void USDScene::boundHash( double time, IECore::MurmurHash &h ) const
14631481
{
14641482
if( pxr::UsdGeomBoundable boundable = pxr::UsdGeomBoundable( m_location->prim ) )
14651483
{
1466-
h.append( m_root->fileName() );
1484+
h.append( m_root->uniqueId() );
14671485
appendPrimOrMasterPath( m_location->prim, h );
14681486
if( boundable.GetExtentAttr().ValueMightBeTimeVarying() )
14691487
{
@@ -1476,7 +1494,7 @@ void USDScene::transformHash( double time, IECore::MurmurHash &h ) const
14761494
{
14771495
if( pxr::UsdGeomXformable xformable = pxr::UsdGeomXformable( m_location->prim ) )
14781496
{
1479-
h.append( m_root->fileName() );
1497+
h.append( m_root->uniqueId() );
14801498
appendPrimOrMasterPath( m_location->prim, h );
14811499

14821500
bool mightBeTimeVarying = xformable.TransformMightBeTimeVarying();
@@ -1573,7 +1591,7 @@ void USDScene::attributesHash( double time, IECore::MurmurHash &h ) const
15731591

15741592
if( haveAttributes || haveMaterials )
15751593
{
1576-
h.append( m_root->fileName() );
1594+
h.append( m_root->uniqueId() );
15771595

15781596
if( haveAttributes )
15791597
{
@@ -1594,7 +1612,7 @@ void USDScene::objectHash( double time, IECore::MurmurHash &h ) const
15941612
{
15951613
if( ObjectAlgo::canReadObject( m_location->prim ) )
15961614
{
1597-
h.append( m_root->fileName() );
1615+
h.append( m_root->uniqueId() );
15981616
appendPrimOrMasterPath( m_location->prim, h );
15991617
if( ObjectAlgo::objectMightBeTimeVarying( m_location->prim ) )
16001618
{
@@ -1604,13 +1622,13 @@ void USDScene::objectHash( double time, IECore::MurmurHash &h ) const
16041622
}
16051623
void USDScene::childNamesHash( double time, IECore::MurmurHash &h ) const
16061624
{
1607-
h.append( m_root->fileName() );
1625+
h.append( m_root->uniqueId() );
16081626
appendPrimOrMasterPath( m_location->prim, h );
16091627
}
16101628

16111629
void USDScene::hierarchyHash( double time, IECore::MurmurHash &h ) const
16121630
{
1613-
h.append( m_root->fileName() );
1631+
h.append( m_root->uniqueId() );
16141632
appendPrimOrMasterPath( m_location->prim, h );
16151633
h.append( time );
16161634
}

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,60 @@ def testInstancesShareHashes( self ) :
474474
self.assertEqual( instance0.hash( hashType, 0 ), instance1.hash( hashType, 0 ) )
475475
self.assertEqual( notInstance.hash( hashType, 0 ), instance0.hash( hashType, 0 ) )
476476

477+
def testInstancePrototypeHashesNotReused( self ) :
478+
479+
# The original intent of this test was to check that we assign consistent hashes to prototypes when
480+
# opening and closing the same file ( which triggers USD to randomly shuffle the prototype names ).
481+
482+
# The solution we've ended up with is instead of assigning consistent hashes, each instance of the
483+
# file gets it's own unique hashes, and is basically treated separately. This allows us to just
484+
# use the prototype names in the hash, since we force the hash to be unique anyway.
485+
486+
usedHashes = set()
487+
488+
for i in range( 100 ):
489+
scene = IECoreScene.SceneInterface.create(
490+
os.path.dirname( __file__ ) + "/data/severalInstances.usda",
491+
IECore.IndexedIO.OpenMode.Read
492+
)
493+
494+
instance0 = scene.child( "instance0" )
495+
instance0Child = instance0.child( "world" )
496+
instance10 = scene.child( "instance10" )
497+
instance10Child = instance10.child( "model" ).child( "assembly" )
498+
499+
h1 = instance0Child.hash( scene.HashType.TransformHash, 1 )
500+
h2 = instance10Child.hash( scene.HashType.TransformHash, 1 )
501+
502+
self.assertNotEqual( h1, h2 )
503+
504+
self.assertNotIn( h1, usedHashes )
505+
for j in range( 1, 10 ):
506+
instanceJ = scene.child( "instance%i" % j )
507+
self.assertEqual( h1, instanceJ.child( "world" ).hash( scene.HashType.TransformHash, 1 ) )
508+
del instanceJ
509+
510+
self.assertNotIn( h2, usedHashes )
511+
for j in range( 11, 20 ):
512+
instanceJ = scene.child( "instance%i" % j )
513+
self.assertEqual( h2, instanceJ.child( "model" ).child( "assembly" ).hash( scene.HashType.TransformHash, 1 ) )
514+
del instanceJ
515+
516+
usedHashes.add( h1 )
517+
usedHashes.add( h2 )
518+
519+
# We must carefully delete everything in order to reliably trigger USD randomly switching the
520+
# prototype names around ( I guess waiting for the garbage collector means we might not be
521+
# fully closing the file before we open it again? Weird, but seems reproducible ).
522+
# This is no longer really crucial to this test, since we just force every instance of the file
523+
# to get unique hashes rather than trying to keep prototypes hashing the same, but I'm trying to
524+
# document as much intent as possible here, in case we consider a different solution in the future.
525+
del instance0Child
526+
del instance0
527+
del instance10Child
528+
del instance10
529+
del scene
530+
477531
def testGeometricInterpretation( self ) :
478532

479533
primitive = IECoreScene.PointsPrimitive( IECore.V3fVectorData( [ imath.V3f( 0 ) ] ) )
@@ -1049,8 +1103,6 @@ def testSetHashes( self ):
10491103
readRoot = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
10501104
readRoot2 = IECoreScene.SceneInterface.create( anotherFileName, IECore.IndexedIO.OpenMode.Read )
10511105

1052-
readRoot3 = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
1053-
10541106
A = readRoot.child('A')
10551107
Ap = readRoot.child('A')
10561108

@@ -1064,9 +1116,6 @@ def testSetHashes( self ):
10641116
A2 = readRoot2.child('A')
10651117
self.assertNotEqual( A.hashSet("dummySetA"), A2.hashSet("dummySetA") )
10661118

1067-
A3 = readRoot3.child('A')
1068-
self.assertEqual( A.hashSet("dummySetA"), A3.hashSet("dummySetA") )
1069-
10701119
def testSetsAtRoot( self ) :
10711120

10721121
fileName = os.path.join( self.temporaryDirectory(), "test.usda" )
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#usda 1.0
2+
3+
def Xform "instance0" (
4+
prepend references = @./sphere.usda@
5+
instanceable = true
6+
)
7+
{ }
8+
def Xform "instance1" (
9+
prepend references = @./sphere.usda@
10+
instanceable = true
11+
)
12+
{ }
13+
def Xform "instance2" (
14+
prepend references = @./sphere.usda@
15+
instanceable = true
16+
)
17+
{ }
18+
def Xform "instance3" (
19+
prepend references = @./sphere.usda@
20+
instanceable = true
21+
)
22+
{ }
23+
def Xform "instance4" (
24+
prepend references = @./sphere.usda@
25+
instanceable = true
26+
)
27+
{ }
28+
def Xform "instance5" (
29+
prepend references = @./sphere.usda@
30+
instanceable = true
31+
)
32+
{ }
33+
def Xform "instance6" (
34+
prepend references = @./sphere.usda@
35+
instanceable = true
36+
)
37+
{ }
38+
def Xform "instance7" (
39+
prepend references = @./sphere.usda@
40+
instanceable = true
41+
)
42+
{ }
43+
def Xform "instance8" (
44+
prepend references = @./sphere.usda@
45+
instanceable = true
46+
)
47+
{ }
48+
def Xform "instance9" (
49+
prepend references = @./sphere.usda@
50+
instanceable = true
51+
)
52+
{ }
53+
def Xform "instance10" (
54+
prepend references = @./kind.usda@
55+
instanceable = true
56+
)
57+
{ }
58+
def Xform "instance11" (
59+
prepend references = @./kind.usda@
60+
instanceable = true
61+
)
62+
{ }
63+
def Xform "instance12" (
64+
prepend references = @./kind.usda@
65+
instanceable = true
66+
)
67+
{ }
68+
def Xform "instance13" (
69+
prepend references = @./kind.usda@
70+
instanceable = true
71+
)
72+
{ }
73+
def Xform "instance14" (
74+
prepend references = @./kind.usda@
75+
instanceable = true
76+
)
77+
{ }
78+
def Xform "instance15" (
79+
prepend references = @./kind.usda@
80+
instanceable = true
81+
)
82+
{ }
83+
def Xform "instance16" (
84+
prepend references = @./kind.usda@
85+
instanceable = true
86+
)
87+
{ }
88+
def Xform "instance17" (
89+
prepend references = @./kind.usda@
90+
instanceable = true
91+
)
92+
{ }
93+
def Xform "instance18" (
94+
prepend references = @./kind.usda@
95+
instanceable = true
96+
)
97+
{ }
98+
def Xform "instance19" (
99+
prepend references = @./kind.usda@
100+
instanceable = true
101+
)
102+
{ }

0 commit comments

Comments
 (0)