USD, Fabric, and USDRT
Overview
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 |
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 |
|
|
|
Array |
|
|
|
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 |
|
|
|
Array |
|
|
|
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 |
USDRT |
|
---|---|---|---|
Open-source repo |
N/A |
N/A |
|
Published Binaries |
N/A |
N/A |
|
PyPi Package |
N/A |
N/A |
|
NVIDIA internal repo |
|||
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 |