Attribute Reads and Writes#
ovrtx reads and writes runtime stage attributes using DLPack tensors. The dtype and shape must match the USD attribute schema. Scalar APIs operate on one value per prim. Array APIs operate on variable-length arrays such as mesh points or relationships.
Tensor Layout#
Python uses NumPy-style trailing dimensions for vectors and matrices. C uses
DLDataType::lanes for multi-component values.
USD value |
Python shape |
C shape and dtype |
|---|---|---|
|
|
|
|
|
|
|
|
|
4x4 transform semantic for N prims |
|
|
|
|
|
Reading Attributes#
# Read a scalar attribute — one value per prim. The returned ManagedDLTensor
# is DLPack-compatible; np.from_dlpack() gives a zero-copy numpy view.
tensor = renderer.read_attribute(
attribute_name="omni:rtx:rtpt:maxBounces",
prim_paths=["/Render/Camera"],
)
values = np.from_dlpack(tensor)
# Arrays are returned as dict[prim_path, ManagedDLTensor]. Iteration order
# matches the input prim_paths so you can zip() against the request.
tensors = renderer.read_array_attribute(
attribute_name="points",
prim_paths=["/World/Plane"],
)
for path, tensor in tensors.items():
values = np.from_dlpack(tensor)
print(f"{path}: {values.size} elements, dtype={values.dtype}")
// Describe the attribute to read: one prim, name, element type matching
// how the runtime stores it (maxBounces is a 32-bit unsigned integer).
ovx_string_t rp = ovx_str("/Render/Camera");
DLDataType uint32_type = {kDLUInt, 32, 1};
ovrtx_binding_desc_or_handle_t binding = ovrtx_make_binding_desc(
&rp, 1, ovx_str("omni:rtx:rtpt:maxBounces"), OVRTX_SEMANTIC_NONE, uint32_type);
// Enqueue the read. Pass NULL for read_dest so ovrtx allocates internal storage.
ovrtx_read_handle_t read_handle = 0;
ovrtx_enqueue_result_t eq = ovrtx_read_attribute(renderer_, &binding, nullptr, &read_handle);
ASSERT_API_SUCCESS(eq.status);
ovrtx_op_wait_result_t wait_result;
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
ASSERT_NO_OP_ERRORS(wait_result);
// Fetch the DLPack tensor(s). Scalar reads produce buffer_count == 1 with
// shape [prim_count].
ovrtx_read_output_t output{};
ASSERT_API_SUCCESS(ovrtx_fetch_read_result(renderer_, read_handle, ovrtx_timeout_infinite, &output).status);
ASSERT_EQ(output.buffer_count, 1u);
ASSERT_EQ(output.prim_count, 1u);
DLTensor const& t = output.buffers[0].dl;
uint32_t value = *static_cast<uint32_t const*>(t.data);
// Release the read result when done. The output pointers are invalidated.
ovrtx_cuda_sync_t no_sync{};
ASSERT_API_SUCCESS(ovrtx_release_read_result(renderer_, output.map_handle, no_sync).status);
// Array attributes are variable-length per prim. The dtype in the binding
// is (code, bits, lanes) — lanes expresses multi-component element types.
// `points` is float3[], so request float32 with lanes=3: one element per
// point, three lanes per point. Override is_array=true (the helper default
// is false).
ovx_string_t prim = ovx_str("/World/Plane");
DLDataType point3f_type = {kDLFloat, 32, 3};
ovrtx_binding_desc_or_handle_t binding = ovrtx_make_binding_desc(
&prim, 1, ovx_str("points"), OVRTX_SEMANTIC_NONE, point3f_type);
binding.binding_desc.attribute_type.is_array = true;
ovrtx_read_handle_t read_handle = 0;
ovrtx_enqueue_result_t eq = ovrtx_read_attribute(renderer_, &binding, nullptr, &read_handle);
ASSERT_API_SUCCESS(eq.status);
ovrtx_op_wait_result_t wait_result;
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
ASSERT_NO_OP_ERRORS(wait_result);
// For arrays the output has one tensor per prim (buffer_count == prim_count).
ovrtx_read_output_t output{};
ASSERT_API_SUCCESS(ovrtx_fetch_read_result(renderer_, read_handle, ovrtx_timeout_infinite, &output).status);
ASSERT_TRUE(output.is_array);
ASSERT_EQ(output.prim_count, 1u);
ASSERT_EQ(output.buffer_count, 1u);
DLTensor const& t = output.buffers[0].dl;
// The Plane has 4 float3 points. The C API returns lane-based attribute
// tensors, so this is shape=[4], dtype={kDLFloat, 32, 3}.
ASSERT_EQ(t.ndim, 1);
ASSERT_EQ(t.shape[0], 4);
ASSERT_EQ(t.dtype.code, kDLFloat);
ASSERT_EQ(t.dtype.bits, 32u);
ASSERT_EQ(t.dtype.lanes, 3u);
int64_t element_count = t.shape[0] * t.dtype.lanes;
ovrtx_cuda_sync_t no_sync{};
ASSERT_API_SUCCESS(ovrtx_release_read_result(renderer_, output.map_handle, no_sync).status);
Python reads can also write directly into a caller-provided DLPack destination, including CUDA destinations:
# Pre-allocate the destination. The read writes directly into `dest`; the
# returned tensor is a handle to the same memory — both aliases are valid.
# The dtype must match how the runtime stores the attribute.
dest = np.empty((1,), dtype=np.uint32)
renderer.read_attribute(
attribute_name="omni:rtx:rtpt:maxBounces",
prim_paths=["/Render/Camera"],
dest=dest,
)
# `dest` now holds the attribute value.
# Allocate a CUDA destination through Warp (any DLPack-compatible CUDA
# allocator works). The read writes directly into GPU memory; pass a CUDA
# stream handle so the read is ordered on the caller's stream.
dest = wp.empty(1, dtype=wp.uint32, device="cuda:0")
stream = wp.Stream(device=dest.device)
renderer.read_attribute(
attribute_name="omni:rtx:rtpt:maxBounces",
prim_paths=["/Render/Camera"],
dest=dest,
cuda_stream=stream.cuda_stream,
)
wp.synchronize_stream(stream)
Writing Attributes#
# point3f[] is a variable-length array of 3-component float vectors.
# Express it as a 2-D ndarray with shape=(M, 3); the trailing 3 is the
# vector dimension, not a lane count.
points = np.array(
[
[-50.0, 0.0, -50.0],
[50.0, 0.0, -50.0],
[-50.0, 0.0, 50.0],
[50.0, 0.0, 50.0],
],
dtype=np.float32,
) # shape=(4, 3)
renderer.write_array_attribute(
prim_paths=["/World/Plane"],
attribute_name="points",
tensors=[points],
)
tensors = renderer.read_array_attribute(
attribute_name="points",
prim_paths=["/World/Plane"],
)
values = np.from_dlpack(tensors["/World/Plane"])
assert values.shape == (4, 3)
renderer.write_array_attribute(
["/World/Plane"],
"omni:docTokens",
[["sensor", "validated"]],
is_token=True,
prim_mode=ovrtx.PrimMode.CREATE_NEW,
)
ovrtx_binding_desc_or_handle_t binding_ref{};
binding_ref.binding_handle = binding_handle;
double matrix[16];
make_xform(14.0, matrix);
size_t count = 1;
DLTensor tensor = ovrtx_make_write_cpu_tensor(matrix, &count, mat_type);
ovrtx_input_buffer_t buffer{};
buffer.tensors = &tensor;
buffer.tensor_count = 1;
eq = ovrtx_write_attribute(renderer_, &binding_ref, &buffer, OVRTX_DATA_ACCESS_SYNC);
ASSERT_API_SUCCESS(eq.status);
docs_wait_no_errors(renderer_, eq.op_index);
Data Access#
Synchronous writes copy data before the call returns. Asynchronous writes may access the caller’s memory later during stream execution, so the source tensor must remain alive until the operation completes. String data supports only synchronous access.
Python exposes this through DataAccess.SYNC and DataAccess.ASYNC. C uses
the access mode argument to ovrtx_write_attribute().
Type Notes#
Use
read_array_attribute/write_array_attributefor USD array attributes and relationships.Semantics are write-side conversion hints. Attribute reads use raw storage layout and
OVRTX_SEMANTIC_NONE.Quaternion tensor order is
(i, j, k, real)even though USDA authors values as(real, i, j, k).stringattributes are represented as UTF-8 byte arrays. String arrays are not supported; usetoken[]for string-like arrays.Python can write token strings directly. C can create and resolve token ids through the path dictionary.
Scalar
assetvalues are supported as token pairs in C. Asset arrays and timecode attributes are not supported as runtime attributes.
C Convenience Helpers#
For path, token, and transform attributes, prefer helpers in
<ovrtx/ovrtx_attributes.h> where available. For token strings:
ovx_string_t prim = ovx_str("/World/Plane");
ovx_string_t purpose = ovx_str("guide");
ovrtx_enqueue_result_t eq =
ovrtx_set_token_attributes(renderer_, &prim, 1, ovx_str("purpose"), &purpose);
ASSERT_API_SUCCESS(eq.status);
docs_wait_no_errors(renderer_, eq.op_index);
Troubleshooting#
Match the runtime dtype, not the Python or C default numeric type.
Array writes in Python take one tensor per prim.
PrimMode.EXISTING_ONLYskips missing prims; useMUST_EXISTwhen a missing prim should be an error.In C, binding descriptors borrow path storage. Keep the strings and arrays alive until the operation that uses the descriptor has completed.
Generic authored USD attributes require
customLayerData.populateAllAuthoredAttributes = trueon the root layer.