Skip to content

Commit 8a3a9ea

Browse files
committed
USDScene : Load bounds from UsdGeomModelAPI extents hints
This can provide significant performance improvements where the ModelAPI has been used to provide cached extents. This knocks around 30% off the time taken to open the file and compute the root bound of the ALab in Gaffer. If USD composition time is omitted so only the bound computation is measured in isolation, a full 95% is knocked off the time. In the case of the ALab, the new root bound is also far more reasonable for some reason - I suspect the model extents are masking a bad authored extent on a leaf object elsewhere in the scene. This has the potential to be surprising for any folks who have authored inaccurate model extents in the past, so I've made it possible to turn off the behaviour using an environment variable. I'd like to default things on in Cortex itself, but we can debate the appropriate default for Gaffer when we next update the Cortex version.
1 parent fdfd188 commit 8a3a9ea

File tree

3 files changed

+93
-21
lines changed

3 files changed

+93
-21
lines changed

Changes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
10.5.x.x (relative to 10.5.8.0)
22
========
33

4+
Improvements
5+
------------
46

7+
- USDScene : `hasBound()` and `readBound()` now use `UsdGeomModelAPI` extents hints if they are available. This behaviour can be disabled by setting the `IECOREUSD_USE_MODELAPI_BOUNDS` environment variable to a value of `0`.
58

69
10.5.8.0 (relative to 10.5.7.1)
710
========

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ IECORE_PUSH_DEFAULT_VISIBILITY
5858
#include "pxr/usd/usdGeom/camera.h"
5959
#include "pxr/usd/usdGeom/gprim.h"
6060
#include "pxr/usd/usdGeom/metrics.h"
61+
#include "pxr/usd/usdGeom/modelAPI.h"
6162
#include "pxr/usd/usdGeom/pointInstancer.h"
6263
#include "pxr/usd/usdGeom/primvar.h"
6364
#include "pxr/usd/usdGeom/primvarsAPI.h"
@@ -528,6 +529,33 @@ Imath::M44d localTransform( const pxr::UsdPrim &prim, pxr::UsdTimeCode time )
528529
return result;
529530
}
530531

532+
static const bool g_useModelAPIBounds = []() -> bool {
533+
const char *c = getenv( "IECOREUSD_USE_MODELAPI_BOUNDS" );
534+
if( !c )
535+
{
536+
return true;
537+
}
538+
return strcmp( c, "0" );
539+
}();
540+
541+
pxr::UsdAttribute boundAttribute( const pxr::UsdPrim &prim )
542+
{
543+
if( auto boundable = pxr::UsdGeomBoundable( prim ) )
544+
{
545+
return boundable.GetExtentAttr();
546+
}
547+
548+
if( g_useModelAPIBounds )
549+
{
550+
if( auto modelAPI = pxr::UsdGeomModelAPI( prim ) )
551+
{
552+
return modelAPI.GetExtentsHintAttr();
553+
}
554+
}
555+
556+
return pxr::UsdAttribute();
557+
}
558+
531559
// Used to assign a unique hash to each USD file. Using a global counter rather than the file name
532560
// means that we treat the same file as separate if it is closed and reopened. This means it's not
533561
// a problem if USD changes things when a file is reopened. USD appears to not in general guarantee
@@ -820,30 +848,32 @@ std::string USDScene::fileName() const
820848

821849
Imath::Box3d USDScene::readBound( double time ) const
822850
{
823-
pxr::UsdGeomBoundable boundable = pxr::UsdGeomBoundable( m_location->prim );
824-
if( !boundable )
825-
{
826-
return Imath::Box3d();
827-
}
828-
829-
pxr::UsdAttribute attr = boundable.GetExtentAttr();
851+
pxr::UsdAttribute attr = boundAttribute( m_location->prim );
830852
if( !attr.IsValid() )
831853
{
832854
return Imath::Box3d();
833855
}
834856

835857
pxr::VtArray<pxr::GfVec3f> extents;
836-
attr.Get<pxr::VtArray<pxr::GfVec3f> >( &extents, m_root->getTime( time ) );
858+
attr.Get( &extents, m_root->getTime( time ) );
837859

838-
if( extents.size() == 2 )
860+
// When coming from UsdGeomModelAPI, `extents` may contain several bounds,
861+
// on a per-purpose basis. Take the union, since the SceneInterface API only
862+
// has a single bound per location.
863+
Imath::Box3d result;
864+
for( size_t i = 0; i + 1 < extents.size(); i += 2 )
839865
{
840-
return Imath::Box3d(
841-
DataAlgo::fromUSD( extents[0] ),
842-
DataAlgo::fromUSD( extents[1] )
866+
const Imath::Box3d b(
867+
DataAlgo::fromUSD( extents[i] ),
868+
DataAlgo::fromUSD( extents[i+1] )
843869
);
870+
if( !b.isEmpty() )
871+
{
872+
result.extendBy( b );
873+
}
844874
}
845875

846-
return Imath::Box3d();
876+
return result;
847877
}
848878

849879
ConstDataPtr USDScene::readTransform( double time ) const
@@ -873,14 +903,7 @@ void USDScene::path( SceneInterface::Path &p ) const
873903

874904
bool USDScene::hasBound() const
875905
{
876-
pxr::UsdGeomBoundable boundable = pxr::UsdGeomBoundable( m_location->prim );
877-
pxr::UsdAttribute attr;
878-
879-
if( boundable )
880-
{
881-
attr = boundable.GetExtentAttr();
882-
}
883-
906+
pxr::UsdAttribute attr = boundAttribute( m_location->prim );
884907
return attr.IsValid();
885908
}
886909

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4211,5 +4211,51 @@ def testTreatLightAsPointOrLine( self ) :
42114211
self.assertEqual( root.child( "sphereLight" ).readAttribute( "light", 0 ), sphereLightShader )
42124212
self.assertEqual( root.child( "cylinderLight" ).readAttribute( "light", 0 ), cylinderLightShader )
42134213

4214+
def testModelBound( self ) :
4215+
4216+
fileName = os.path.join( self.temporaryDirectory(), "modelBound.usda" )
4217+
4218+
stage = pxr.Usd.Stage.CreateNew( fileName )
4219+
pxr.UsdGeom.Xform.Define( stage, "/withoutModelAPI" )
4220+
pxr.UsdGeom.Xform.Define( stage, "/withModelAPI" )
4221+
pxr.UsdGeom.Xform.Define( stage, "/withModelAPIAndExtent" )
4222+
4223+
pxr.UsdGeom.ModelAPI.Apply( stage.GetPrimAtPath( "/withModelAPI" ) )
4224+
modelAPI = pxr.UsdGeom.ModelAPI.Apply( stage.GetPrimAtPath( "/withModelAPIAndExtent" ) )
4225+
modelAPI.SetExtentsHint( [ ( 1, 2, 3 ), ( 4, 5, 6 ) ] )
4226+
4227+
stage.GetRootLayer().Save()
4228+
del stage
4229+
4230+
root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
4231+
self.assertFalse( root.hasBound() )
4232+
4233+
self.assertFalse( root.child( "withoutModelAPI" ).hasBound() )
4234+
self.assertFalse( root.child( "withModelAPI" ).hasBound() )
4235+
self.assertTrue( root.child( "withModelAPIAndExtent" ).hasBound() )
4236+
self.assertEqual( root.child( "withModelAPIAndExtent" ).readBound( 0 ), imath.Box3d( imath.V3d( 1, 2, 3 ), imath.V3d( 4, 5, 6 ) ) )
4237+
4238+
def testPerPurposeModelBound( self ) :
4239+
4240+
fileName = os.path.join( self.temporaryDirectory(), "testPerPurposeModelBound.usda" )
4241+
4242+
stage = pxr.Usd.Stage.CreateNew( fileName )
4243+
pxr.UsdGeom.Xform.Define( stage, "/group" )
4244+
cube = pxr.UsdGeom.Cube.Define( stage, "/group/proxy" )
4245+
cube.CreatePurposeAttr().Set( "proxy" )
4246+
4247+
bboxCache = pxr.UsdGeom.BBoxCache( pxr.Usd.TimeCode( 0 ), [ "default", "render", "proxy", "guide" ] )
4248+
modelAPI = pxr.UsdGeom.ModelAPI.Apply( stage.GetPrimAtPath( "/group" ) )
4249+
modelAPI.SetExtentsHint( modelAPI.ComputeExtentsHint( bboxCache ) )
4250+
4251+
stage.GetRootLayer().Save()
4252+
del stage
4253+
4254+
root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
4255+
self.assertFalse( root.hasBound() )
4256+
4257+
self.assertTrue( root.child( "group" ).hasBound() )
4258+
self.assertEqual( root.child( "group" ).readBound( 0 ), imath.Box3d( imath.V3d( -1 ), imath.V3d( 1 ) ) )
4259+
42144260
if __name__ == "__main__":
42154261
unittest.main()

0 commit comments

Comments
 (0)