Source code for isaaclab_ov.renderers.ovrtx_mapping
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Stream-safe helpers for OVRTX attribute-binding mappings.
Filling a CUDA-mapped OVRTX attribute buffer from Warp is only correct when the commit at unmap
time is ordered against the Warp stream that produced the data: OVRTX's API contract requires the
mapped data to be ready when the unmap's CUDA sync signals, and an unmap without a sync performs
no synchronization at all. The ``with binding.map(...)`` form cannot carry that sync -- its
``__exit__`` takes no arguments -- so GPU writes through a mapping should use
:func:`map_attribute_for_warp_writes` instead of the binding's own context manager.
"""
from __future__ import annotations
from collections.abc import Iterator
from contextlib import contextmanager
from typing import Any
import warp as wp
def cuda_device_id(device: str) -> int:
"""CUDA device index parsed from a Warp device string, e.g. ``"cuda:1"`` -> ``1``.
Shared by the attribute mappings below and by the render product's ``deviceIds``, so both
resolve the renderer's device the same way.
TODO: A bare ``"cuda"`` parses to ``0`` while Warp enqueues fill work on its *current* CUDA
device, so the mapping and the fill can target different GPUs on multi-GPU processes. The
split predates this helper and is kept here to avoid a behavior change; a follow-up caches
the resolved Warp device on the renderer instead of re-deriving it from strings.
Args:
device: Warp CUDA device string (``"cuda"`` or ``"cuda:<index>"``).
Returns:
The parsed CUDA device index, ``0`` when the string carries none.
"""
parts = device.split(":")
return int(parts[1]) if len(parts) > 1 else 0
[docs]
@contextmanager
def map_attribute_for_warp_writes(binding: Any, device: wp.Device | str, dtype: Any) -> Iterator[wp.array]:
"""Map ``binding`` for CUDA writes and yield its buffer as a Warp array; commit after the fill.
The caller fills the yielded array with Warp work enqueued on ``device``'s current stream (the
default for ``wp.launch``/``wp.copy``). On exit -- error or not -- the mapping is unmapped with
that stream as the CUDA sync, so OVRTX's commit of the mapped data waits for the fill on the
GPU instead of racing it. OVRTX has no discard path (unmap always commits), so a failed fill
still publishes whatever landed in the buffer.
Args:
binding: OVRTX attribute binding (from ``bind_attribute``) whose buffer is written.
device: Warp CUDA device, or its string alias, on which the fill work runs.
dtype: Warp dtype the mapped tensor is viewed as (e.g. ``wp.mat44d``).
Yields:
The mapped buffer as a zero-copy Warp array, valid only inside the ``with`` block.
"""
# Deferred so importing this module (e.g. through the package's lazy exports) cannot initialize
# ovrtx without the guarded environment ``ovrtx_renderer`` establishes (OVRTX_SKIP_USD_CHECK,
# actionable install error). Any real ``binding`` was created through that path, so ovrtx is
# already imported by the time this runs.
from ovrtx import Device # noqa: PLC0415
warp_device = wp.get_device(device)
attr_mapping = binding.map(device=Device.CUDA, device_id=warp_device.ordinal)
try:
yield wp.from_dlpack(attr_mapping.tensor, dtype=dtype)
finally:
attr_mapping.unmap(stream=warp_device.stream.cuda_stream)