USD, Fabric, and USDRT

Overview

Organization of USD, Fabric, and USDRT

USD is Pixar’s library that allows large-scale scenes to be composed from hundreds or thousands of component layers and modified using non-destructive operations. Layers are backed by files on disk or a web service like Nucleus or AWS. The USD library enables authoring and editing of individual layers and inspection of the composed state of those layers, the USD stage.

Fabric is the Omniverse library that enables high-performance creation, modification, and access of scene data, as well as communication of scene data between CPU and GPU and other Fabric clients on a network. Data in Fabric can be populated from the local composed USD stage, but may also be generated procedurally or populated over the network. Fabric also provides a ringbuffer between simulation and one or more renderers to allow them to run at different rates. Fabric indexes data and provides a query API to allow systems such as physics and rendering to get only the data they need, without having to traverse the scene each frame.

USDRT Scenegraph API is an Omniverse API that mirrors the USD API (and is pin-compatible in most cases), but reads and writes data to and from Fabric instead of USD. It is designed to enable low-cost transitions for developers and technical artists to use Fabric in their code by starting with existing USD code or writing new code with USD APIs they already know, in either C++ or Python.

Features at a Glance

USD, Fabric, and USDRT Features

USD

Fabric

USDRT

Developer

Pixar

NVIDIA

NVIDIA

Data view

Pre- and post-composition

Post-composition only

Post-composition only

API Languages

C++, Python

C++

C++, Python

Python bindings

boost::python

N/A

pybind11

ABI versioning

No guarantees, ABI may change with each release

Versioned Carbonite interfaces

ONI, periodic stable forward-compatible releases

Vectorized APIs

No

Yes

Not yet

Cost to locate (e.g. all prims of a type, properties by name)

O(n) - traversal of entire stage

O(1) - query

O(1) / O(n) - query by type or applied API / traversal of entire stage for others

GPU data synchronization

No

Yes

Partial

Data persistence

File-based storage

Transient, in-memory only

Transient, in-memory only

Data write speed

Slow

Fastest

Fast

Which API to use

USD, Fabric, and USDRT each provide different capabilities for interacting with scene data in Omniverse. Here’s some guidelines about which API is most appropriate for your project or extension.

USD

The USD API can be used for both pre- and post- composition operations. That is, reading and authoring data directly from/to individual layers (pre-composition) and inspecting the result of composing those layers into a stage (post-composition). As a general rule, it is fast to read data from USD, but can be slow to write data to USD.

Use the USD API when you are:

  • authoring persistent data (new assets and scenes) to be saved to one or more layers

  • inspecting the composition of a stage (ex: checking a property value across all that layers where it is authored, querying composition arcs on a prim, discovering all the instances of a scenegraph prototype)

  • modifying the composition of a stage (ex: changing a variantSet selection, enabling or disabling instancing for a prim, adding a new reference to a prim)

Fabric

The Fabric API operates on a post-composition view of the USD stage. Fabric cannot modify the composition of a stage (ex: change a variantSet selection) or inspect pre-composition data from individual USD layers. Data may be authored to Fabric that does not exist in USD (both prims and properties), and Fabric may not contain all prims and properties that exist on the USD stage. Fabric manages synchronization of data to and from the GPU for GPU-accelerated operations and rendering.

Use the Fabric API when you:

  • are inspecting the composed state of the USD stage

  • are authoring transient data like simulation results, procedurally generated geometry, and interactive transform updates

  • want to author data rapidly, to be stored back to USD at a later time or not at all

  • want the option to use vectorized APIs for reading or writing data (see vectorized example below)

  • want to read or modify data on the GPU

  • want to quickly query the data to process it every frame, for example to get all the physics prims, all the renderable prims, or all the prims that have a particular set of attributes

  • are using C++

USDRT

The USDRT API provides a USD-like API on top of Fabric. If you are already familiar with the USD API, you can apply your existing knowledge to immediately begin working with Fabric.

Use the USDRT API when you:

  • want to work with Fabric (see above - transient data, fast data writes)

    AND

  • want a USD-like API

  • don’t need access to vectorized APIs

  • want fast queries by prim type or applied API, or are ok with traversing the stage to find the prims you want to process or know their paths without searching

  • are using C++ or Python

Code examples

A few code examples are provided here to demonstrate how to accomplish a task with the USD, Fabric, and USDRT APIs.

USD and USDRT samples are compared side-by-side, as the intention of the USDRT Scenegraph API is to be as compatible with the USD API as possible.

Fabric does not provide a Python API, so there are no Python samples for Fabric code. USDRT is the designated API for interacting with Fabric through Python.

These samples are part of the usdrt repository unit test suites, so validation asserts are mixed in throughout. For C++ the tests use doctest, so the tests use the CHECK() macro. For Python the tests use unittest, so you will see checks like self.assertTrue() and self.assertEqual().

Get and modify the displayColor property on one prim

For this example, we look at modifying an attribute on a prim with a known path. In all cases, this data acn be accessed and modified directly.

C++

USD and USDRT

The code for the USD sample and the USDRT sample is the same, with the exception of the namespace using directive. However, the USDRT sample is reading data from Fabric, while the USD sample is reading data from the USD stage.

USD

USDRT

PXR_NAMESPACE_USING_DIRECTIVE;

SdfPath path("/Cornell_Box/Root/Cornell_Box1_LP/White_Wall_Back");
UsdPrim prim = stage->GetPrimAtPath(path);
TfToken attrName = UsdGeomTokens->primvarsDisplayColor;
UsdAttribute attr = prim.GetAttribute(attrName);

// Get the value of displayColor on White_Wall_Back,
// which is mid-gray on this stage
VtArray<GfVec3f> result;
attr.Get(&result);
CHECK(result.size() == 1);
CHECK(result[0] == GfVec3f(0.5, 0.5, 0.5));

// Set the value of displayColor to red,
// and verify the change by getting the value
VtArray<GfVec3f> red = { GfVec3f(1, 0, 0) };
attr.Set(red);
attr.Get(&result);
CHECK(result[0] == GfVec3f(1, 0, 0));
using namespace usdrt;

SdfPath path("/Cornell_Box/Root/Cornell_Box1_LP/White_Wall_Back");
UsdPrim prim = stage->GetPrimAtPath(path);
TfToken attrName = UsdGeomTokens->primvarsDisplayColor;
UsdAttribute attr = prim.GetAttribute(attrName);

// Get the value of displayColor on White_Wall_Back,
// which is mid-gray on this stage
VtArray<GfVec3f> result;
attr.Get(&result);
CHECK(result.size() == 1);
CHECK(result[0] == GfVec3f(0.5, 0.5, 0.5));

// Set the value of displayColor to red,
// and verify the change by getting the value
VtArray<GfVec3f> red = { GfVec3f(1, 0, 0) };
attr.Set(red);
attr.Get(&result);
CHECK(result[0] == GfVec3f(1, 0, 0));
Fabric

Fabric’s StageReaderWriter class provides separate APIs for accessing non-array-typed attributes and array-typed attributes. As noted in the code comments, there are also separate APIs for accessing data as a read-only (const) pointer, a writable (non-const) pointer, or a read/write (non-const) pointer - using the write or read/write APIs will mark the attribute as potentially modified in Fabric’s change tracking system. Fabric change tracking subscribers may then take appropriate action based on potentially modified attribute values.

Attribute type

Read-only API

Write API

Read/Write API

Non-array

getAttributeRd()

getAttributeWr()

getAttribute()

Array

getArrayAttributeRd()

getArrayAttributeWr()

getArrayAttribute()

Because primvars are always represented by an array (even when the array only contains a single item, i.e. constant interpolation), the getArrayAttributeRd() and getArrayAttributeWr() APIs are used in this example.

using namespace omni::fabric;
using namespace usdrt;

const Path primPath("/Cornell_Box/Root/Cornell_Box1_LP/White_Wall_Back");
const Token attrName("primvars:displayColor");

// Get the value of displayColor on White_Wall_Back,
// which is mid-gray on this stage
// Note - the "Rd" API (getArrayAttributeRd) returns a span
// of const items, which is therefor read-only
// and Fabric does not track that a change was potentially made here
gsl::span<const GfVec3f> displayColor = stageReaderWriter.getArrayAttributeRd<GfVec3f>(primPath, attrName);
CHECK(displayColor.size() == 1);
CHECK(displayColor[0] == GfVec3f(0.5, 0.5, 0.5));

// Set the value of displayColor to red
// Note - the "Wr" API (getArrayAttributeWr) returns a span
// with non-const items, and this can be used to write
// new values directly to Fabric. Using the "Wr" APIs
// indicates to Fabric that a change may have been made to this
// attribute, which Fabric will track
gsl::span<GfVec3f> displayColorWr = stageReaderWriter.getArrayAttributeWr<GfVec3f>(primPath, attrName);
displayColorWr[0] = GfVec3f(1, 0, 0);

// Validate that displayColor was updated to red
// using the previous read-only query
CHECK(displayColor[0] == GfVec3f(1, 0, 0));

Python

USD and USDRT

Like the C++ example, the code for the USD sample and the USDRT sample is the same, with the exception of the packages import namespace. And also like the C++ example, the USDRT sample is reading data from Fabric, while the USD sample is reading data from the USD stage.

USD

USDRT


from pxr import Gf, Sdf, Usd, Vt

path = "/Cornell_Box/Root/Cornell_Box1_LP/White_Wall_Back"
prim = stage.GetPrimAtPath(path)
attr = prim.GetAttribute("primvars:displayColor")

# Get the value of displayColor on White_Wall_Back,
# which is mid-gray on this stage
result = attr.Get()
self.assertEqual(len(result), 1)
self.assertEqual(result[0], Gf.Vec3f(0.5, 0.5, 0.5))

# Set the value of displayColor to red,
# and verify the change by getting the value
attr.Set(Vt.Vec3fArray([Gf.Vec3f(1, 0, 0)]))
result = attr.Get()
self.assertEqual(len(result), 1)
self.assertEqual(result[0], Gf.Vec3f(1, 0, 0))


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

path = "/Cornell_Box/Root/Cornell_Box1_LP/White_Wall_Back"
prim = stage.GetPrimAtPath(path)
attr = prim.GetAttribute(UsdGeom.Tokens.primvarsDisplayColor)

# Get the value of displayColor on White_Wall_Back,
# which is mid-gray on this stage
result = attr.Get()
self.assertEqual(len(result), 1)
self.assertEqual(result[0], Gf.Vec3f(0.5, 0.5, 0.5))

# Set the value of displayColor to red,
# and verify the change by getting the value
attr.Set(Vt.Vec3fArray([Gf.Vec3f(1, 0, 0)]))
result = attr.Get()
self.assertEqual(len(result), 1)
self.assertEqual(result[0], Gf.Vec3f(1, 0, 0))

Find all Mesh prims and change the displayColor property

In this example, we want to modify the displayColor property on every Mesh prim on the stage. This demonstrates a key performance feature of Fabric. Prim discovery with Fabric is very fast (O(1)) because Fabric’s internal storage organizes prims by shared sets of properties and metadata. Prim discovery with USD can be quite expensive (O(n)) because it requires traversing the entire stage to discover all prims with a specific type or specific attributes. USDRT also supports fast queries (O(1)) with Fabric by prim type or applied API schema.

C++

USD and USDRT

In this example, we see a key performance advantage in USDRT over USD. With USDRT, querying the stage for prims by type or applied API schema has a constant cost. With USD, this requires a traversal of the entire stage.

USD

USDRT

PXR_NAMESPACE_USING_DIRECTIVE;

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

for (UsdPrim prim : stage->Traverse())
{
    if (prim.GetTypeName() == mesh)
    {
        prim.GetAttribute(attrName).Set(red);
    }
}
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);
    }
}
Fabric

Efficient discovery of prims in Fabric is accomplished using the StageReaderWriter::find() API, takes a set of property names and types (represented by AttrNameAndType objects) and returns a PrimBucketList containing all Fabric buckets (data groupings) that match those requirements. Note that in Fabric, prim type is also represented by a property of the type tag.

Fabric’s StageReaderWriter::find() is utilized in USDRT by a few APIs:

  • UsdStage::GetPrimsWithTypeName()

  • UsdStage::GetPrimsWithAppliedAPIName()

  • UsdStage::GetPrimsWithTypeAndAppliedAPIName()

Fabric queries are currently more general that USDRT queries because they support querying by attribute name and type in addition to prim type and applied API schema. Fabric also allows you to categorize your query inputs by prim types and attribute names that should be present in all, any, or none of the results.

This first example demonstrates a non-vectorized approach, where each property is accessed individually using the getArrayAttributeWr method.

using namespace omni::fabric;
using namespace usdrt;

// Get every Mesh prim with a displayColor attribute in Fabric
const Token displayColorName("primvars:displayColor");
const GfVec3f red(1, 0, 0);
AttrNameAndType displayColor(Type(BaseDataType::eBool), displayColorName);
AttrNameAndType mesh(Type(BaseDataType::eTag, 1, 0, AttributeRole::ePrimTypeName), Token("Mesh"));
PrimBucketList buckets = stageReaderWriter.findPrims({ displayColor, mesh });

// Iterate over the buckets that match the query
for (size_t bucketId = 0; bucketId < buckets.bucketCount(); bucketId++)
{
    auto primPaths = stageReaderWriter.getPathArray(buckets, bucketId);

    for (Path primPath : primPaths)
    {
        gsl::span<GfVec3f> displayColorWr =
            stageReaderWriter.getArrayAttributeWr<GfVec3f>(primPath, displayColorName);
        displayColorWr[0] = red;
    }
}

Efficient access and modification of properties with Fabric can be accomplished using its vectorized APIs. Internally, Fabric stores data so that all properties of the same name and type are stored adjacent to each-other in memory as an array, for each group of prims with matching sets of properties (aka bucket). This allows access to all properties in a bucket with a single lookup and simple pointer arithmetic, which is fast, GPU-friendly, and straightforward to parallelize at scale. The example below demonstrates vectorized attribute access with Fabric:

using namespace omni::fabric;
using namespace usdrt;

// Get every Mesh prim with a displayColor attribute in Fabric
const Token displayColorName("primvars:displayColor");
const GfVec3f red(1, 0, 0);
AttrNameAndType displayColor(Type(BaseDataType::eBool), displayColorName);
AttrNameAndType mesh(Type(BaseDataType::eTag, 1, 0, AttributeRole::ePrimTypeName), Token("Mesh"));
PrimBucketList buckets = stageReaderWriter.findPrims({ displayColor, mesh });

// Iterate over the buckets that match the query
for (size_t bucketId = 0; bucketId < buckets.bucketCount(); bucketId++)
{
    // Get the array of displayColor attributes from the bucket
    auto dcArray = stageReaderWriter.getArrayAttributeArrayWr<GfVec3f>(buckets, bucketId, displayColorName);
    for (gsl::span<GfVec3f> color : dcArray)
    {
        // Update the attribute to red
        color[0] = red;
    }
}

Like the non-vectorized versions, the vectorized APIs have separate methods for array and non-array typed attributes, and read, write, and read/write versions of those methods. Using a write or read/write method will mark a property as potentially modified in Fabric’s change tracker for all prims in the bucket.

Attribute type

Read-only API

Write API

Read/Write API

Non-array

getAttributeArrayRd()

getAttributeArrayWr()

getAttributeArray()

Array

getArrayAttributeArrayRd()

getArrayAttributeArrayWr()

getArrayAttributeArray()

Python

USD and USDRT

USD

USDRT


from pxr import Gf, Usd, UsdGeom, Vt

red = Vt.Vec3fArray([Gf.Vec3f(1, 0, 0)])
for prim in stage.Traverse():
    if prim.GetTypeName() == "Mesh":
        prim.GetAttribute(UsdGeom.Tokens.primvarsDisplayColor).Set(red)


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)

Get and modify a material binding relationship

USDRT and Fabric also support relationship-type properties.

C++

USD and USDRT

USD

USDRT

PXR_NAMESPACE_USING_DIRECTIVE;

SdfPath path("/Cornell_Box/Root/Cornell_Box1_LP/Green_Wall/Green1SG");
UsdPrim prim = stage->GetPrimAtPath(path);
TfToken relName = UsdShadeTokens->materialBinding;
UsdRelationship rel = prim.GetRelationship(relName);

// The currently bound Material prim, and a new
// Material prim to bind
const SdfPath oldMtl("/Cornell_Box/Root/Looks/Green1SG");
const SdfPath newMtl("/Cornell_Box/Root/Looks/Red1SG");

// Get the first target
CHECK(rel.HasAuthoredTargets());
SdfPathVector targets;
rel.GetTargets(&targets);
CHECK(targets[0] == oldMtl);

// Update the relationship to target
// a different material
SdfPathVector newTargets = { newMtl };
rel.SetTargets(newTargets);
SdfPathVector updatedTargets;
rel.GetTargets(&updatedTargets);
CHECK(updatedTargets[0] == newMtl);
using namespace usdrt;

SdfPath path("/Cornell_Box/Root/Cornell_Box1_LP/Green_Wall/Green1SG");
UsdPrim prim = stage->GetPrimAtPath(path);
TfToken relName = UsdShadeTokens->materialBinding;
UsdRelationship rel = prim.GetRelationship(relName);

// The currently bound Material prim, and a new
// Material prim to bind
const SdfPath oldMtl("/Cornell_Box/Root/Looks/Green1SG");
const SdfPath newMtl("/Cornell_Box/Root/Looks/Red1SG");

// Get the first target
CHECK(rel.HasAuthoredTargets());
SdfPathVector targets;
rel.GetTargets(&targets);
CHECK(targets[0] == oldMtl);

// Update the relationship to target
// a different material
SdfPathVector newTargets = { newMtl };
rel.SetTargets(newTargets);
SdfPathVector updatedTargets;
rel.GetTargets(&updatedTargets);
CHECK(updatedTargets[0] == newMtl);
Fabric

Fabric uses the same APIs to work with both attributes and relationships, with omni::fabric::Path as the template type: StageReaderWriter::getAttribute<Path>().

An important note is that Fabric and USDRT only support a single relationship target in Kit 104 - this is updated in Kit 105 to support any number of targets on a relationship (like USD).

The example below contains an example of a Fabric API that bridges both Kit releases, which will return the only relationship target in Kit 104, and the first relationship target in Kit 105.

To get all relationship targets in Kit 105, you would use StageReaderWriter::getArrayAttribute<Path>() or the equivalent read-only or write APIs.

using namespace omni::fabric;
using namespace usdrt;

const Path primPath("/Cornell_Box/Root/Cornell_Box1_LP/Green_Wall/Green1SG");
const Token relName("material:binding");

const Path oldMtl("/Cornell_Box/Root/Looks/Green1SG");
const Path newMtl("/Cornell_Box/Root/Looks/Red1SG");

// Get the first target of material:binding
// Note: getAttribute methods have a specialization for relationships
// that will return the first target directly
const Path* target = stageReaderWriter.getAttributeRd<Path>(primPath, relName);
CHECK(*target == oldMtl);

// Update the relationship to target
// a different material
Path* newTarget = stageReaderWriter.getAttributeWr<Path>(primPath, relName);
*newTarget = newMtl;

// Validate that target was updated
CHECK(*target == newMtl);

Python

USD and USDRT

USD

USDRT


from pxr import Gf, Sdf, Usd

path = "/Cornell_Box/Root/Cornell_Box1_LP/Green_Wall/Green1SG"
prim = stage.GetPrimAtPath(path)
rel = prim.GetRelationship("material:binding")

# The currently bound Material prim, and a new
# Material prim to bind
old_mtl = "/Cornell_Box/Root/Looks/Green1SG"
new_mtl = "/Cornell_Box/Root/Looks/Red1SG"

# Get the first target
self.assertTrue(rel.HasAuthoredTargets())
mtl_path = rel.GetTargets()[0]
self.assertEqual(mtl_path, old_mtl)

# Update the relationship to target a different material
rel.SetTargets([new_mtl])
updated_path = rel.GetTargets()[0]
self.assertEqual(updated_path, new_mtl)


from usdrt import Gf, Sdf, Usd, UsdGeom

path = "/Cornell_Box/Root/Cornell_Box1_LP/Green_Wall/Green1SG"
prim = stage.GetPrimAtPath(path)
rel = prim.GetRelationship(UsdShade.Tokens.materialBinding)

# The currently bound Material prim, and a new
# Material prim to bind
old_mtl = "/Cornell_Box/Root/Looks/Green1SG"
new_mtl = "/Cornell_Box/Root/Looks/Red1SG"

# Get the first target
self.assertTrue(rel.HasAuthoredTargets())
mtl_path = rel.GetTargets()[0]
self.assertEqual(mtl_path, old_mtl)

# Update the relationship to target a different material
rel.SetTargets([new_mtl])
updated_path = rel.GetTargets()[0]
self.assertEqual(updated_path, new_mtl)

Working with data on the GPU

Fabric has the capability of synchronizing data between CPU memory and GPU memory. This can be useful for staging data on the GPU for running CUDA kernels to process it in a highly parallelized manner.

USDRT has some initial support for Fabric’s GPU data through its VtArray implementation - Fabric GPU data array-typed attributes may be accessed though VtArray. Additionally, GPU data from Python objects that implement the CUDA Array interface (such as warp) can be used to populate a VtArray and copy the data to Fabric with a direct copy on the GPU.

C++

using namespace omni::fabric;
using namespace usdrt;

UsdStageRefPtr stage = UsdStage::Open("./data/usd/tests/cornell.usda");

const Path primPath("/Cornell_Box/Root/Cornell_Box1_LP/White_Wall_Back");
const Token attrName("points");

// Get the points attribute value from USDRT
VtArray<GfVec3f> usdrtPoints;
UsdPrim mesh = stage->GetPrimAtPath(SdfPath(primPath));
UsdAttribute attr = mesh.GetAttribute(TfToken(attrName));
attr.Get(&usdrtPoints);

// Nobody has accessed the GPU mirror for the points
// attribute yet, so VtArray reports that there is no
// Fabric GPU data available
CHECK(!usdrtPoints.HasFabricGpuData());

std::vector<GfVec3f> newPoints = { { 0, 0, 0 }, { 0, 1, 0 }, { 1, 1, 0 }, { 1, 0, 0 } };

// Update the points on the GPU with Fabric APIs
// Note - USDRT itself does not yet directly support
// moving data to and from GPU, but this will be added
// in a forthcoming release
StageReaderWriter stageReaderWriter(stage->GetStageReaderWriterId());
GfVec3f** points = stageReaderWriter.getAttributeWrGpu<GfVec3f*>(primPath, attrName);
REQUIRE(points);
REQUIRE(*points);
cudaError_t err = cudaMemcpy(*points, newPoints.data(), newPoints.size() * sizeof(GfVec3f), cudaMemcpyHostToDevice);
REQUIRE(err == cudaSuccess);

// USDRT can query the attribute value again.
// This time, HasFabricGpuData returns true, indicating
// a GPU mirror for the attribute in Fabric
attr.Get(&usdrtPoints);
CHECK(usdrtPoints.HasFabricGpuData());

// We can use cudaMemcpy to copy GPU data from Fabric
// into memory on the host, in this case, a new vector,
// using the GetGpuData method on VtArray
std::vector<GfVec3f> verifyPoints(4);
REQUIRE(usdrtPoints.GetGpuData());
err = cudaMemcpy(
    verifyPoints.data(), usdrtPoints.GetGpuData(), verifyPoints.size() * sizeof(GfVec3f), cudaMemcpyDeviceToHost);
REQUIRE(err == cudaSuccess);

CHECK(verifyPoints[0] == GfVec3f(0, 0, 0));
CHECK(verifyPoints[1] == GfVec3f(0, 1, 0));
CHECK(verifyPoints[2] == GfVec3f(1, 1, 0));
CHECK(verifyPoints[3] == GfVec3f(1, 0, 0));

Python


import numpy as np
import warp as wp

from usdrt import Gf, Sdf, Usd, Vt

def warp_array_from_cuda_array_interface(a):
    cai = a.__cuda_array_interface__
    return wp.types.array(
        dtype=wp.vec3,
        length=cai["shape"][0],
        capacity=cai["shape"][0] * wp.types.type_size_in_bytes(wp.vec3),
        ptr=cai["data"][0],
        device="cuda",
        owner=False,
        requires_grad=False,
    )

stage = Usd.Stage.CreateInMemory("test.usda")

prim = stage.DefinePrim(Sdf.Path("/prim"))
attr = prim.CreateAttribute("points", Sdf.ValueTypeNames.Point3fArray, True)

# First create a warp array from numpy
points = np.zeros(shape=(1024, 3), dtype=np.float32)
for i in range(1024):
    for j in range(3):
        points[i][j] = i * 3 + j

warp_points = wp.array(points, device="cpu")

with wp.ScopedCudaGuard():
    gpu_warp_points = warp_points.to("cuda")
    warp_ref = weakref.ref(gpu_warp_points)

    # Create a VtArray from a warp array
    vt_points = Vt.Vec3fArray(gpu_warp_points)

    # Set the Fabric attribute which will make a CUDA copy
    # from warp to Fabric
    attr.Set(vt_points)
    wp.synchronize()

    # Retrieve a new Fabric VtArray from USDRT
    new_vt_points = attr.Get()
    self.assertTrue(new_vt_points.HasFabricGpuData())

    # Create a warp array from the VtArray
    new_warp_points = warp_array_from_cuda_array_interface(new_vt_points)

    # Delete the fabric VtArray to ensure the data was
    # really copied into warp
    del new_vt_points

    # Convert warp points back to numpy
    new_points = new_warp_points.numpy()

# Now compare the round-trip through USDRT and GPU was a success
self.assertEqual(points.shape, new_points.shape)
for i in range(1024):
    for j in range(3):
        self.assertEqual(points[i][j], new_points[i][j])

Interoperability between USD and USDRT

There are scenarios where it can be useful to leverage both USD and USDRT together. This is possible with a little bit of careful namespace management and some helpers for converting between a few key types.

C++

PXR_NS::UsdStageRefPtr usdStage = PXR_NS::UsdStage::Open("./data/usd/tests/cornell.usda");
// Get USD stage Id from global stage cache in UsdUtils
PXR_NS::UsdStageCache::Id usdStageId = PXR_NS::UsdUtilsStageCache::Get().Insert(usdStage);

// Get Fabric stage Id from USD stage Id
omni::fabric::UsdStageId stageId(usdStageId.ToLongInt());

// Create USDRT stage pointer using Attach w/ Fabric stage Id
usdrt::UsdStageRefPtr stage = usdrt::UsdStage::Attach(stageId);

usdrt::SdfPathVector meshPaths = stage->GetPrimsWithTypeName(usdrt::TfToken("Mesh"));

for (const usdrt::SdfPath meshPath : meshPaths)
{
    // Convert usdrt SdfPath to USD SdfPath
    const PXR_NS::SdfPath& usdMeshPath = omni::fabric::toSdfPath(omni::fabric::PathC(meshPath));

    // Now you can query the USD stage
    PXR_NS::UsdPrim usdPrim = usdStage->GetPrimAtPath(usdMeshPath);

    // ...and use APIs that are not represented in USDRT
    if (usdPrim.HasVariantSets())
    {
        std::cout << "Found Mesh with variantSet: " << usdMeshPath.GetString() << std::endl;
    }
}

Python

from pxr import Sdf, Usd, UsdUtils

from usdrt import Usd as RtUsd

usd_stage = Usd.Stage.Open(TEST_DIR + "/data/usd/tests/cornell.usda")
usd_stage_id = UsdUtils.StageCache.Get().Insert(usd_stage)

stage = RtUsd.Stage.Attach(usd_stage_id.ToLongInt())

mesh_paths = stage.GetPrimsWithTypeName("Mesh")

for mesh_path in mesh_paths:
    # strings can be implicitly converted to SdfPath in Python
    usd_prim = usd_stage.GetPrimAtPath(str(mesh_path))

    if usd_prim.HasVariantSets():
        print(f"Found Mesh with variantSet: {mesh_path}")

Availability

USD, Fabric, and USDRT Availability

USD

Fabric

USDRT

Open-source repo

https://github.com/PixarAnimationStudios/USD

N/A

N/A

Published Binaries

https://developer.nvidia.com/usd

N/A

N/A

PyPi Package

usd_core

N/A

N/A

NVIDIA internal repo

omniverse/USD

omniverse/kit

omniverse/usdrt

packman package

nv_usd

kit_sdk

usdrt

Kit-103

USD-20.08

carb.flatcache.plugin

N/A

Kit-104

USD-20.08

carb.flatcache.plugin

USDRT Scenegraph API extension

Kit-105

USD-22.11

omni.fabric.plugin

USDRT Scenegraph API extension