Attribute Mapping#

Note

Python stage attribute mapping uses ovstage query and map-group APIs. Renderer map/unmap methods are deprecated compatibility APIs. Refer to skills/update-0_3-0_4-python/SKILL.md.

Mapping an attribute gives application code direct access to its stage buffer. This avoids a copy when code writes directly into the buffer.

The lifecycle is:

  1. Map the attribute.

  2. Write into the mapped tensor while the mapping is active.

  3. Unmap the attribute, passing CUDA stream or event synchronization when GPU work wrote the data.

CPU Mapping#

with stage.map_attribute(query, attribute, ordinal=2) as mapping:
    mapping.wait()
    group = mapping.fetch_next()
    matrices = np.from_dlpack(group.dlpack(0)).reshape(1, 4, 4)
    matrices[0, 3, 0] = 10.0
stage.advance_write_floor(2, ovstage.Scope.ALL).wait()
// Zero-copy write via map/unmap: map_attribute hands back writable
// storage for each matched prim; the caller fills it, then unmap_attribute
// commits and releases the map session.
ovx_string_or_token_t attr_ref{};
attr_ref.token = attr_token;
ovstage_map_desc_t map_desc{};
map_desc.attribute = attr_ref;
map_desc.dtype = {kDLFloat, 64, 16};
map_desc.semantic = OVSTAGE_SEMANTIC_MATRIX;
map_desc.prim_mode = OVSTAGE_PRIM_MODE_UPSERT;

ovstage_map_handle_t map_handle = OVSTAGE_INVALID_MAP_HANDLE;
ovstage_enqueue_result_t mq = ovstage_map_attribute(
    stage_, query_handle, &map_desc, /*ordinal=*/2,
    /*element_sizes=*/nullptr, /*element_count=*/0, &map_handle);
ASSERT_EQ(mq.status, OVSTAGE_OK) << format_ovstage_last_error();
docs_wait_ovstage_no_errors(stage_, mq.op_index);

ovstage_map_group_t map_group{};
ASSERT_EQ(ovstage_fetch_map_next(stage_, map_handle, OVSTAGE_TIMEOUT_INFINITE, &map_group),
          OVSTAGE_OK);
double* matrix = static_cast<double*>(map_group.data.tensors[0].data);
matrix[0] = 1.0;
matrix[5] = 1.0;
matrix[10] = 1.0;
matrix[15] = 1.0;
matrix[12] = 15.0; // translation.x under USD row-vector convention

// Commit all pending groups and release the map handle.
ovstage_cuda_sync_t no_sync{};
ovstage_enqueue_result_t uq = ovstage_unmap_attribute(stage_, map_handle, no_sync);
ASSERT_EQ(uq.status, OVSTAGE_OK) << format_ovstage_last_error();
docs_wait_ovstage_no_errors(stage_, uq.op_index);
docs_ovstage_advance_write_floor(stage_, 2);

CUDA Mapping#

The deprecated renderer wrapper can map attributes to CUDA memory for GPU-side writes. The example below documents its stream-synchronization requirements.

mapping = renderer.map_attribute(
    ["/World/Plane"],
    "omni:xform",
    dtype="float64",
    shape=(4, 4),
    device=ovrtx.Device.CUDA,
)
tensor = wp.from_dlpack(mapping.tensor, dtype=wp.mat44d)
stream = wp.Stream(device=tensor.device)
wp.launch(_set_xform_translation_x, dim=1, inputs=[tensor, wp.float64(6.0)], stream=stream)
mapping.unmap(stream=stream.cuda_stream)

Explicit Unmap#

Context managers are preferred in Python, but explicit async unmap is available when the application needs to coordinate mapping lifetime manually.

mapping = stage.map_attribute(query, attribute, ordinal=2)
mapping.wait()
group = mapping.fetch_next()
matrices = np.from_dlpack(group.dlpack(0)).reshape(1, 4, 4)
matrices[0, 3, 0] = 9.0
op = mapping.unmap()
op.wait()
stage.advance_write_floor(2, ovstage.Scope.ALL).wait()

Limits and Lifetime#

  • Ovstage maps ragged array attributes when element_sizes supplies one element count per queried prim. Omit element_sizes for fixed-size attributes.

  • The tensor returned by a mapping is valid only until unmap. Copy data if it must outlive the mapping.

  • For deprecated renderer CUDA mappings, pass a stream or event on unmap so ovrtx knows when GPU writes are complete.

  • Do not pass CUDA sync objects for CPU mappings.

  • Multiple mappings can be outstanding; effects are applied in unmap order.