Hi,
I have a fundamental question regarding native instancing in Hydra 1.0.
Specs:
- Maya USD 0.33.0
- OpenUSD 25.11
- Windows
I have an asset that are built out of instanceable sub-pieces, and those assets
then get instanced a lot in a layer. See attached StressTest_HierarchyRefs.usda
StressTest_HierarchyRefs.usda is the top layer and contains a four-level chain of references, instanceable = true at every level, with a single UsdGeomCube at the bottom: 125 towers, 25 floors in each tower, 8 rows in each floor, 8 cubes in each row. In total 166 instance-able prims.
In Maya, when I move one of those instances in the top layer ( e.g Tower_24 ) the frame rate drops drastically that is almost unusable to interact with the scene. See below

On the other hand, if I reduce the level of instancing and only keep the top layer to be instance able ( see StressTest.usda ) then I get a better frame rate ~ 7 fps.

I’ve spent sometimes in UsdImagingInstanceAdapter and I think I understand
why, but I’d rather have someone who knows this code tell me whether I’ve read it
correctly:
I think the first is that a moved instance isn’t tracked individually. Its transform
lives in an array on the instancer, so the only thing Hydra can be told is that
the array is stale:
// instanceAdapter.cpp:1278, ProcessPropertyChange
// Transform changes to instance prims end up getting folded into the
// "hydra:instanceTransforms" instance-rate primvar.
if (UsdGeomXformable::IsTransformationAffectedByAttrNamed(propertyName)) {
return HdChangeTracker::DirtyPrimvar;
}
Then when an instance is populated, the adapter walks down the whole chain of nested instancers and registers a dependency on every one of them:
std::queue<SdfPath> depInstancePaths;
depInstancePaths.push(instancePath);
while (!depInstancePaths.empty()) {
...
index->AddDependency(depInstancerPath, prim);
index->MarkInstancerDirty(depInstancerPath,
HdChangeTracker::DirtyPrimvar | HdChangeTracker::DirtyInstanceIndex);
for (SdfPath const& nestedInstance : depInstancerData.nestedInstances) {
depInstancePaths.push(nestedInstance);
}
}
Once an instance is dirty, its transform array is rebuilt from scratch, sized to
the full flattened count rather than to whatever actually changed:
if (requestedBits & HdChangeTracker::DirtyPrimvar) {
VtMatrix4dArray instanceXforms;
if (_ComputeInstanceTransforms(prim, &instanceXforms, time)) {
// instanceAdapter.cpp:721
void Initialize(size_t numInstances) { result.resize(numInstances); ... }
_ComputeInstanceTransformFn::operator()
GfMatrix4d xform(1.0);
for (UsdPrim const& prim : instanceContext) {
xform = xform * adapter->GetTransform(prim, prim.GetPath(), time, ignoreRootTransform);
}
I added some debugging code in UsdImagingInstanceAdapter::_RunForAllInstancesToDraw
TF_WARN("_RunForAllInstancesToDraw [%s] %s rows=%zu",
ArchGetDemangled(typeid(Functor)).c_str(),
instancer.GetPath().GetText(),
instancerData->numInstancesToDraw);
fn->Initialize(instancerData->numInstancesToDraw);
This function is called from multiple sites ( e.g _ComputeInstanceMapFn, _PopulateInstanceSelectionFn, _GetScenePrimPathsFn )
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_ComputeInstanceMapFn] /Root/Tower_0 rows=125
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_PopulateInstanceSelectionFn] /Root/Tower_0 rows=125
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_ComputeInstanceMapFn] /__Prototype_5/Row_0 rows=25000
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_PopulateInstanceSelectionFn] /__Prototype_5/Row_0 rows=25000
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_ComputeInstanceMapFn] /__Prototype_6/Floor_0 rows=3125
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_PopulateInstanceSelectionFn] /__Prototype_6/Floor_0 rows=3125
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_ComputeInstanceMapFn] /__Prototype_7/Cube_0 rows=200000
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_PopulateInstanceSelectionFn] /__Prototype_7/Cube_0 rows=200000
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_ComputeInstanceMapFn] /__Prototype_7/Cube_0 rows=200000
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_GetScenePrimPathsFn] /__Prototype_7/Cube_0 rows=200000
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_ComputeInstanceMapFn] /__Prototype_7/Cube_0 rows=200000
// Warning: _RunForAllInstancesToDraw [UsdImagingInstanceAdapter::_ComputeInstanceMapFn] /__Prototype_7/Cube_0 rows=200000
rows(Tower) = 125 = 125
rows(Floor) = 25 × 125 = 3,125
rows(Row) = 8 × 25 × 125 = 25,000
rows(Cube) = 8 × 8 × 25 × 125 = 200,000
Am I interpreting this correctly? does a transform edit on one top-level instance dirty every nested instances, with each one rebuilding its whole instanceTransforms?
Any help is appreciated.
TowerOfCubes_HierarchyRefs.usda (5.6 KB)
TowerOfCubes.usda (268.9 KB)
StressTest_HierarchyRefs.usda (30.2 KB)
StressTest.usda (28.5 KB)
Row.usda (1.8 KB)
Floor.usda (1.8 KB)
Cube.usda (118 Bytes)