Fast Stage Queries with USDRT Scenegraph API

About

usdrt-6.0.6 adds new APIs to the usdrt::UsdStage implementation that leverage the capability of Fabric to do constant-time queries of data in Fabric. This document reviews those APIs, their behavior, and how you can leverage them to increase performance in your own application or extension.

Query the stage for prims by type or applied API schema

There are three new APIs to do fast queries of the stage for prims of a given type, prims with a specific applied API schema, or both. These are specific to USDRT’s UsdStage implementation, and leverage Fabric’s unique data layout to return information about the stage without requiring a complete stage traversal with per-prim inquiries.

  • std::vector<SdfPath> UsdStage::GetPrimsWithTypeName(TfToken typeName)

  • std::vector<SdfPath> UsdStage::GetPrimsWithAppliedAPIName(TfToken apiName)

  • std::vector<SdfPath> UsdStage::GetPrimsWithTypeAndAppliedAPIName(TfToken typeName, TfTokenVector apiNames)

The first call to any of these APIs will populate Fabric with a minimal representation of the USD stage if necessary. Subsequent calls will leverage the existing data in Fabric and query Fabric directly.

Query by prim type

Both of these examples discover all Mesh prims, and set the primvars:displayColor attribute if it exists on the prim. Note that these APIs return a vector of SdfPaths, so an additional call to UsdStage::GetPrimAtPath is necessary to access the related UsdPrim.

C++

using namespace usdrt;

const TfToken mesh("Mesh");
const TfToken attrName = UsdGeomTokens->primvarsDisplayColor;
const VtArray<GfVec3f> red = { GfVec3f(1, 0, 0) };

const SdfPathVector meshPaths = stage->GetPrimsWithTypeName(mesh);
for (SdfPath meshPath : meshPaths)
{
    UsdPrim prim = stage->GetPrimAtPath(meshPath);
    if (prim.HasAttribute(attrName))
    {
        prim.GetAttribute(attrName).Set(red);
    }
}

Python


from usdrt import Gf, Sdf, Usd, UsdGeom, Vt

red = Vt.Vec3fArray([Gf.Vec3f(1, 0, 0)])
meshPaths = stage.GetPrimsWithTypeName("Mesh")
for meshPath in meshPaths:
    prim = stage.GetPrimAtPath(meshPath)
    if prim.HasAttribute(UsdGeom.Tokens.primvarsDisplayColor):
        prim.GetAttribute(UsdGeom.Tokens.primvarsDisplayColor).Set(red)

It is also possible to search by inherited types. This example discovers all prims where “doubleSided” is true for the stage.


from usdrt import Gf, Sdf, Usd, UsdGeom

gPrimPaths = stage.GetPrimsWithTypeName("Gprim")
attr = UsdGeom.Tokens.doubleSided
doubleSided = []
for gPrimPath in gPrimPaths:
    prim = stage.GetPrimAtPath(gPrimPath)
    if prim.HasAttribute(attr) and prim.GetAttribute(attr).Get():
        doubleSided.append(prim)

Query by applied API schema

It may be useful in some cases to discover every prim where a particular API schema has been applied. It is possible to query by individual API schema, or by a list of API schemas for a specific prim type. The following examples from the USDRT Scenegraph unit tests demonstrate the concept:

from usdrt import Gf, Sdf, Usd

stage = Usd.Stage.Open(TEST_DIR + "/data/usd/tests/cornell.usda")
self.assertTrue(stage)

paths = stage.GetPrimsWithAppliedAPIName("ShapingAPI")
self.assertEqual(len(paths), 2)

paths = stage.GetPrimsWithAppliedAPIName("CollectionAPI:lightLink")
self.assertEqual(len(paths), 5)
from usdrt import Gf, Sdf, Usd

stage = Usd.Stage.Open(TEST_DIR + "/data/usd/tests/cornell.usda")
self.assertTrue(stage)

paths = stage.GetPrimsWithTypeAndAppliedAPIName("Mesh", ["CollectionAPI:test"])
self.assertEqual(len(paths), 1)

Get the world bound for the stage

An additional API exists that leverages world extent data in Fabric set by Fabric Scene Delegate or the RtBoundable schema to compute the bounding extent for the stage:

GfRange3d UsdStage::GetStageExtent()

If no world extent data exists in Fabric, this API will populate it from USD using a multi-threaded compute and Fabric queries to target only Boundable prims directly.

from usdrt import Gf, Sdf, Usd

stage = Usd.Stage.Open(TEST_DIR + "/data/usd/tests/cornell.usda")
self.assertTrue(stage)

bound = stage.GetStageExtent()
self.assertTrue(Gf.IsClose(bound.GetMin(), Gf.Vec3d(-333.8142, -249.9100, -5.444), 0.01))
self.assertTrue(Gf.IsClose(bound.GetMax(), Gf.Vec3d(247.1132, 249.9178, 500.0), 0.01))

Using stage queries for optimal performance

The key benefit of these query APIs is that they only populate Fabric with the minimal set of data from the underlying USD stage required for each query, and once populated, querying Fabric is extremely fast. Fabric queries are performed in constant time, and unlike stage traversal, do not increase relative to prim count on the USD stage.

This performance characteristic is beneficial for modular software architectures where each module may require independent inquiries into the USD stage. For example, consider a hypothetical suite of Kit extensions where each extension needs to do one of the following inquiries into the USD stage in response to a stage-opened event:

  • A BackupLight extension checks if there are any lights on the USD stage, and if not, creates a default light

  • A CharacterAnimator extension finds all of the UsdSkel prims on the USD stage and populates a menu with their names

  • A FastRender extension checks if any Mesh prim names end with the string “_glass” to optimize render settings

  • A ForkliftSimulator extension finds all prims with the PhysicsDriveAPI schema applied to prepare a simulation context

Accomplishing these tasks with the conventional USD API requires each extension to independently perform a complete traversal of the USD stage. However, with the USDRT stage query APIs and underlying Fabric data, at most one USD stage traversal is required for lightweight Fabric population, and subsequent queries after the first leverage the data that has already been populated into Fabric. This makes stage inspection fast and lightweight, and modularity straightforward.